天天看点

探秘Java9之类加载

Java9带来了模块化系统,同时类加载机制也进行了调整,Java9中的类加载器,变化仅仅是ExtClassLoader消失了且多了PlatformClassLoader,JVM规范里5.3 Creation and Loading部分详细描述了类加载,这里简单说下,规范里把类加载器分为两类,一类是由虚拟机提供的启动类加载器,另一类是由用户自定义的类加载器,注意数组的创建不是类加载器创建的,而是由虚拟机直接创建的。而加载又分为两种情况:defining loader和initiating loader,defining loader只加载不初始化,initiating loader是加载并初始化。在运行时一个类或接口是否唯一不是取决于其二进制名称,而是二进制名称和defining其的类加载器的组合,这些和之前保持一致的,那具体区别在哪?其中JVM规范5.3.6 Modules and Layers有详细说明,增加了Layer(层)的概念,用Layer表示模块集,其实Layer和类加载器是对应的,将启动类加载器加载的模块归一Layer,用户自定义类加载器加载的模块归到另一Layer,Layer也有委托的概念。本文从JDK9的源码入手一窥Java9中类加载有了哪些变化。先宏观看下Java中的类加载器:

探秘Java9之类加载

1、关于类加载

Java9之前的类加载已经有很多详细的介绍了,这里主要说明Java9中的类加载机制。

2、类加载核心代码(参见jdk.internal.loader.BuiltinClassLoader#loadClassOrNull(java.lang.String, boolean)):

这段代码可以看出,原有的双亲委派机制受到了模块化的影响,首先如果当前类已经加载了则直接返回,如果没加载,则根据名称找到对应的模块有没有加载,如果对应模块没有加载,则委派给父加载器去加载。如果对应模块已经加载了,则委派给对应模块的加载器去加载,这里需要注意下,在模块里即使使用java.lang.Thread#setContextClassLoader方法改变当前上下文的类加载器,或者在模块里直接使用非当前模块的类加载器去加载当前模块里的类,最终使用的还是加载当前模块的类加载器。

3、Java9虚拟机初始化系统类分成了3个阶段

Java9之前的版本中没有模块化时只有一个初始化,Java9中分成了3个阶段,阶段2是模块化的初始化工作,主要是boot layer的加载,bootlayer里包含的是平台系统依赖的一些模块,阶段3是访问控制设置,类加载器的状态变更等,Java9里的获取系统类加载器时是根据不同的initLevel来做安全校验,Level为4是表示系统初始化ok了,应用调用此方法获取AppClassLoader时校验反射安全,而虚拟机在0-2的状态里则不校验,直接返回AppClassLoader。

initLevel从0到1的过程也就是上面说的阶段1,1到2的过程就是阶段2,2到3再到4是在阶段3里做的,3到4的过程如下:

Java7里的java.lang.ClassLoader#getSystemClassLoader的实现是:

Java9里的java.lang.ClassLoader#getSystemClassLoader的实现是:

3、BootClassLoader

启动类加载器,用于加载启动的基础模块类。运行时内存模型如下截图:

探秘Java9之类加载

4、PlatformClassLoader

平台类加载器,用于加载一些平台相关的模块,双亲是BootClassLoader。运行时内存模型如下截图:

探秘Java9之类加载

5、AppClassLoader

应用模块加载器,用于加载应用级别的模块,双亲是PlatformClassLoader。运行时内存模型如下截图:

探秘Java9之类加载

6、packageToModule

全局的已经加载的boot layer的模块集记录Map,key是包名。

7、nameToModule

每个ClassLoader都有一个nameToModule,是用于记录当前ClassLoader加载的模块,一个模块里的类只会由一个ClassLoader来加载。nameToModule是一个MAP,name是模块名,和packageToModule不同。nameToModule的运行时模型如图:

探秘Java9之类加载

8、关于废弃的java.lang.Class#newInstance

Java9之前和Java9使用ClassLoader的方式对比:

Car类:

注意,Car类没有参数为空的构造方式,只有一个带参的构造方法。

运行结果如下:

java.lang.InstantiationException: test.Car

run....

这种按构造方法实例化的方式应该是比较方便的技能了,而且可以看出异常也更细分了。如果Car类没有构造方法,两种方式都可以运行,但明显Java9的这种方式更强大,原先的方式自然会被申明废弃了。