JVM源码分析(三)类加载流程

本文略微记录了jvm的类加载流程。暂时未涉及OOP和Klass相关的知识。

一、java代码的类加载机制

这个大家都懂,双亲委派机制,首先附上类加载器的继承关系

image.png

其中AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader没有父加载器。
我们知道jdk中自配的ClassLoader是,调用其ClassLoader的loadClass方法来完成类加载的

  • 虽然URLClassLoaderAppClassLoader都复写了loadClass方法,但是两者的实现都是做了下类名检查就调用了父类的loadClass方法。因此最后实现是ClassLoader#loadClass.
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

上述代码逻辑大致如下:
先由findLoadedClass->native方法findLoadedClass0,检查是否是已经加载的类,如果没加载过且父加载器不为空,则尝试由父加载器来加载。

如果父加载器为空,那么需要调用findBootstrapClassOrNull->底层的native方法findBootstrapClass来检查是否是顶级类加载器BootstrapClassLoader加载过的类。

如果不是,那么则调用findClass由子类自己去加载。我们看一下 URLClassLoader是怎么干的(此处忽略代码)

URLClassLoader#findClass中,根据类加载器的加载路径(包括jar),找到对应的文件,最后调用defineClass->native方法defineClass1将java层的类注册到jvm中,那么下次再获取的时候就可以从findLoadedClass方法中找到了。

双亲委派模型有以下几个优点:

  • 安全,父加载器优先于子加载器,核心的类库只能由jvm自己去加载。
  • 共享,子加载器可以直接使用父加载器加载过的类,使得系统不会多次加载一个类。

二、jvm中的类加载机制

从上述分析可以得到,java类的核心加载,格式解析,校验均是由底层来完成的。class文件格式可以参考文章记一次解析class文件过程
从实现方式来看,可以分为两大类。1.顶级类加载器bootStrapClassLoader加载jdk核心类库;2.用户代码由native方法defineClass1方式加载到jvm中的类,其中jvm中实现类加载的类是SystemDictionary

2.1 jdk类加载流程

之前在<<JVM源码分析(一) -- java启动流程>>中说到,java在启动时会调用jvm库的create_vm->init_globals方法时,完成一些重要的初始化工作,其中重要的有

  • parse_vm_init_args 解析vm参数,vm参数在里面均可找到。

  • universe_init 初始化gc策略和,全局堆 和tlab等。tlab是在eden区切出一小块,(每个线程均有一个,具体是ThreadLocalAllocBuffer::initialize,没看懂是如何计算的,有人说是256k)。

  • vmSymbols::initialize 创建基础类型(int,boolean...)的klass句柄。

  • SystemDictionary::initialize 加载jdk中重要的类

  • ....

jdk中定义了一些重要的类名为_well_known_klasses,_well_known_klasses的组成可以由systemDictionary.hpp#WK_KLASSES_DO
枚举到,涵盖了Object,String,Class,...一系列重要的类,SystemDictionary::initialize负责将_well_known_klasses中的类都加载到jvm中,流程如下:

  • 遍历顶级加载器的加载路径,用LazyClassPathEntry::open_stream寻找到要加载的类的class文件流

  • 调用ClassFileParser::parseClassFile执行具体的从文件流读取数据,根据class文件格式标准解析class文件,生成对应的instanceKlass对象。

  • 调用SystemDictionary::find_or_define_instance_class->SystemDictionary::update_dictionary->Dictionary::add_klass将生成的Klass对象存起来。Dictionary是个hash表实现,使用的也是开链法解决hash冲突。

2.2 用户自定义类加载流程

除却jvm初始化加载的类,其他类的加载是由java程序触发的,调用native方法defineClass1

流程和初始化加载类流程差不多。由java代码传入文件流句柄和类名,调用底层代码SystemDictionary::resolve_from_stream。其中主要是两件事,1.ClassFileParser::parseClassFile读取文件生成Klass。2.调用SystemDictionary#define_instance_class将类加载到Klass字典中。

2.3 手动测试用户类加载

上面说了jdk中的类是如何被加载到jvm的,下面测试下一个自定义java类的加载流程。
类被加载的时机有许多种:new关键字,Class.forName方法,初始化某个类的子类,调用类的静态方法等等。为了简单测试,下面使用了Class.forName来测试,测试代码

import java.lang.reflect.InvocationTargetException;

import sun.misc.Launcher;
public class Hello {
    public static void main(String[] args) {
        System.out.println("begin");
        try {
            Thread.sleep(10000);
            Class<?> c = Class.forName("printClass");
            Thread.sleep(10000);
            Class<?> c2 = Class.forName("printClass");
            //c.getMethod("print").invoke(c.newInstance());
            c.getMethod("print").invoke(c);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class printClass{
        public  static void print(){
        System.out.println("123");
    }
}

2.3.1 测试类的加载流程

Sleep 10000的作用是为了在attach到进程时,jdk类已经加载完毕,可以直接跑到Class<?> c = Class.forName("printClass"); 后的断点,在jvm参数中加入参数-XX:+TraceClassLoading可以将加载的类打印出来。命令行显示printClass还未加载,堆栈如下

image.png

和上述分析结果大致一样,java部分堆栈有缺失,但是我们可以根据以上的分析补充上缺失的堆栈和流程,依次是

  • 调用'forName'方法加载printClass
  • jvm调用java方法loadClass
  • printClass不是顶级类加载加载的,于是调用URLClassLoader#findClass
  • jvm的DoPrivileged方法
  • jvm校验权限->调用defineClass
  • jvm加载类

随后,jvm调用SystemDictionary::update_dictionary->Dictionary::add_klass将类加入已加载列表。插入字典的堆栈如下

image.png

2.3.2 测试已加载的类查找流程

在第二次调用forName方法时,findLoadedClass已经可以返回结果。查看堆栈

image.png

  • 调用 SystemDictionary::find方法查找类,函数中根据类加载器信息和类名信息计算hash值。
  • 根据hash值从Dictionary类中查找到DictionaryEntry
  • DictionaryEntry中获取Klass信息。

2.3.3 新建对象流程

当java对象调用new关键字新建class对象的时候,会调用到底层的InterpreterRntime::_new(JavaThread* thread, ConstantPool* pool, int index)方法。

IRT_ENTRY(void, InterpreterRntime::_new(JavaThread* thread, ConstantPool* pool, int index))
  Klass* k_oop = pool->klass_at(index, CHECK);
  instanceKlassHandle klass (THREAD, k_oop);

  // Make sure we are not instantiating an abstract klass
  klass->check_valid_for_instantiation(true, CHECK);

  // Make sure klass is initialized
  klass->initialize(CHECK);

  // At this point the class may not be fully initialized
  // because of recursive initialization. If it is fully
  // initialized & has_finalized is not set, we rewrite
  // it into its fast version (Note: no locking is needed
  // here since this is an atomic byte write and can be
  // done more than once).
  //
  // Note: In case of classes with has_finalized we don't
  //       rewrite since that saves us an extra check in
  //       the fast version which then would call the
  //       slow version anyway (and do a call back into
  //       Java).
  //       If we have a breakpoint, then we don't rewrite
  //       because the _breakpoint bytecode would be lost.
  oop obj = klass->allocate_instance(CHECK);
  thread->set_vm_result(obj);
IRT_END
  • 重点是klass_at方法,index参数是该类常量区标识自身类名的位置。最后调用SystemDictionary::resolve_or_fail方法将Klass对象取出。

  • 校验并确保初始化

  • 调用allocate_instance从堆中分配内存.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容