vlambda博客
学习文章列表

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)


一、从JVM源码看类加载器

注:使用的是openjdk8

1.1 Java层面的类加载器


我们都知道在Java类加载中,除了BootStrap加载器,App和Ext加载器都是Java实现的,具体实现在sun.misc.Launcher中:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
在L auncher类中, 有一个静态私有成员变量launcher的赋值,调用本例的构造方法生成一个Launcher实例。 因为launcher是一个类级别属性,所以这个操作会被收敛到类构造器<clinit>()方法,在该类被加载的初始化阶段被执行。


在Launcher的构造方法中,分别初始化了Launcher.ExtClassLoader和Launcher.AppClassLoader加载器,将Launcher.loader属性(ClassLoader.getSystemClassLoader方法返回的就是这个属性)设置为了AppClassLoader,并且将TCCL设置为了AppClassLoader。ExtClassLoader和AppClassLoader初始化如下:
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

从初始化方法就可以看出,ExtClassLoader没有显示设置父加载器,所以其父类加载器是BootStrap,AppClassLoader设置ExtClassLoader为自己的父加载器。

注:加载器的父子关系不是继承上的父子关系,而是通过成员变量引用,以组合的方式实现的父子关系

1.2 JVM是如何启动的


程序的主要入口点在main.c,代码中有大量的条件编译,我们直接看JLI_Launch函数:
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

JLI_Launch函数定义在java.h中,java.c中有该函数的实现,其中会调用LoadJavaVM函数,LoadJavaVM函数对于不同的平台(win、mac、solaris等)有不同的实现,我们这里看看windows的版本,实现在java_md.c中,主要是从jvmpath加载dll,并且初始化调用函数:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

加载Microsoft环境c运行时库后,会根据jvmpath加载jvm的dll文件(在jre目录存有动态链接文件,若将jre\bin\server下的jvm.dll移除,也启动不了JVM)。InvocationFunctions定义在java.h中,有三个JNI函数:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

关于jvmpath,需要再回到JLI_Launch函数中,在该函数中会调用CreateExecutionEnvironment函数创建执行上下文,在其中会初始化jvmpath:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

同样的在java_md.c中找到该函数win平台的实现:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

回到JLI_Launch函数中,除了LoadJavaVM之外,还会选择JRE版本、解析参数、设置classpath等等。准备工作做完之后,进入JVMInit函数执行JVM初始化流程,同样进入win的实现,在java_md.c中:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

然后进入ContinueInNewThread函数,实现在java.c:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

记住这里ContinueInNewThread0函数传的第一个参数是JavaMain。然后我们进入win下的实现,在java_md.c中看看ContinueInNewThread0函数的实现:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

_beginthreadex是一个c运行时库函数,其中:


arg1:安全属性,NULL为默认安全属性

arg2:线程堆栈大小,如果为0,则线程堆栈大小和创建它的线程的相同,在JVM参数- 中可以使用-Xss参数影响,该参数的解析在java.c的AddOption函数中,还包括-Xmx、-Xms的解析,这里就不贴代码了

arg4:传递给线程的参数指针

arg5:线程初始状态


关于STACK_SIZE_PARAM_IS_A_RESERVATION,在os_windows.cpp中能找到说明:

Windows XP added a new flag ‘STACK_SIZE_PARAM_IS_A_RESERVATION’ for CreateThread() that can treat ‘stack_size’ as stack size. However we are not supposed to call CreateThread() directly according to MSDN document because JVM uses C runtime library. The good news is that the flag appears to work with _beginthredex() as well.

我们只需要关心线程调用的函数:JavaMain。该函数实现在java.c中,该函数内容不少,主要是初始化JVM,加载MainClass,调用函数入口方法(main方法)等,下面是部分代码:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

我们进入InitializeJVM函数简单看看初始化虚拟机的逻辑。InitializeJVM方法同样实现在java.c文件中:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

其中调用的CreateJavaVM函数,在前面已经初始化,使用JNI来调用JNI_CreateJavaVM(jvm.dll)。

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

1.3 C++层面的类加载器


前面我们简单介绍了jvm的启动过程,进入jni.cpp中,调用Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);函数创建jvm,对于can_try_again参数,代码中给出了注释说明:

Certain errors during initialization are recoverable and do not prevent this method from being called again at a later time (perhaps with different arguments). However, at a certain point during initialization if an error occurs we cannot allow this function to be called again (or it will crash). In those situations, the ‘canTryAgain’ flag is set to false, which atomically sets safe_to_recreate_vm to 1, such that any new call to JNI_CreateJavaVM will immediately fail using the above logic.

我们直接进入Threads::create_vm函数中,实现在thread.cpp,该函数内容很多,会初始化很多东西,这里只关注类加载器的部分,这部分逻辑在init_globals函数中:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

init_globals的工作是初始化一些公共模块,其中包括类加载器,该函数在init.cpp中:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

初始化类加载器的函数是classLoader_init,该函数在classLoader.cpp:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

1.3.1 初始化BootStrapClassLoader


同样在classLoader.cpp中找到initialize()函数的实现,贴出部分代码:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

我们暂时不管zib library,直接看setup_bootstrap_search_path函数是如何搜索BootStrap加载器的加载目录的:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

该函数中,有两处语句相对比较核心,分别是:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

get_sysclasspath函数


我们首先来看get_sysclasspath,看名字就知道是获取classpath,strdup是一个字符串拷贝函数,不用理会它。我们进入artuments.hpp中看看aget_sysclasspath函数是如何实现的:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

可以看到获取的是_sun_boot_class_path的值,而_sun_boot_class_path是通过函数set_sysclasspath设置的:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

 那么是在哪里设置的呢?我们能在os.cpp中找到答案:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

update_class_path_entry_list函数


现在找到了classpath,需要再看看是如何存储的,update_class_path_entry_list函数的实现还是在classLoader.cpp中:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

从代码中可以看到,每个定义的目录都是以ClassPathEntry为表现形式,以链表结构存储:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

再回到ClassLoader::initialize()函数,关于LazyBootClassLoader,是一个懒加载配置参数,在globals.hpp中配置为true:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

1.3.2 BootStrapClassLoader如何加载类


这里我们直接看ClassLoader::load_classfile(Symbol* h_name, TRAPS)函数就可以了:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

逻辑很简单,代码中加了几个注释,这里就不赘述了~parseClassFile函数定义在classFileParser.cpp中:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

ClassFileParser::parseClassFile函数内容非常多,期间会解析字节码文件,检查魔数、版本号,解析常量池、字段表等等等等,在解析的过程中,解析之后都会对字节码的有效性做检查,比如是否继承了final类等等,代码很长,这里就不分析了。


在所有的解析和检查操作做完之后,就可以通过函数InstanceKlass::allocate_instance_klass在方法区创建Klass*了,这里涉及了二分模型(oop-klass),不是一两句话能说清楚的,先不管。然后会通过函数java_lang_Class::create_mirror初始化静态字段,填充oop_maps等等。

二、总结

根据我们前面的源码分析,可以看到JVM源码层面类加载的主要逻辑在classLoader文件中实现了系统类的加载。本文的最后,将前面的源码分析总结一个简单的走向图:

深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)
深入openjdk源码全面理解Java类加载器(上 -- JVM源码篇)

—— 明日更新 敬请关注 ——

《深入openjdk源码全面理解Java类加载器(下 -- Java源码篇)》


如有侵权请联系删除

     
       
       
     


动动小手,让更多需要的人看到~