JVM(三):l类加载与双亲委派模型

类加载

类加载的过程包括::加载,验证,准备,解析,初始化

img

其中 验证->准备->解析 这个过程叫做连接过程。并且,整个类加载过程并不是按部就班运行的 ,这些阶段通常可以交互混合地进行。

JVM规定了以下五种情形必须对类执行”初始化“(加载,验证,准备,解析等自然需要在此之前开始)

  • 当使用new关键字建立实例,或者访问类中的静态变量或者静态方法
  • 使用reflect包的方法对类进行反射调用的时候,若还没有进行初始化,则需要进行初始化。
  • 当初始化一个类的时候,其父类还没有被初始化,此时需要进行初始化
  • 虚拟机启动时,用户需要指定一个需要执行的主类,虚拟机会先初始化这个主类
  • 当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后
    的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄
    所对应的类没有进行过初始化,则需要先触发其初始化

加载

加载是类加载过程的第一个阶段,它要做的事有:

1.通过一个类的全限定名来获取该类相关的二进制字节流。JVM并没有规定获取方式,所以可以从class文件中获取,也可以从网络中获取,或者由JSP文件生成

2.将字节流所代表的储存结构转化为方法区的运行时数据结构。

3.在方法区生成该类的Class对象,作为该类的访问入口。

验证

验证是连接阶段的第一步,主要是为了确保Class文件的字节流包含的信息是符合需求的,不会对虚拟机造成破坏

1.文件格式验证

这一阶段主要验证字节流是否符合Class文件格式的规范,如:是否以魔数开头;主次版本号是否在JVM的处理范围之内等

2.元数据验证

这一阶段主要是验证字节码描述的信息是否符合Java语言规范。如:这个类是否继承了final修饰的类;若这个类不是抽象类,是否已经实现了父类或者接口中的方法?;类中定义的参数和方法是否和父类发生了冲突?

3.字节码验证

这一阶段是整个验证过程中最复杂的一个阶段。主要为了验证字节码中的信息是否是合法的,符合逻辑的。比如:有没有发生类型转换的错误?;直接把一个对象赋值给了和它毫无继承关系的数据类型上?

4.符号引用检验

最后一个阶段的检验发生在将符号引用转化为直接引用的时候,所以这个验证操作实际上是和解析过程一起执行的。它主要做的验证如下:

  • 符号引用中通过字符串描述的全限定名是否能找到对应的类
  • 在类中是否能找到描述的字段和方法】
  • 符号引用中的类是否真的有权限访问(private protected public)

准备

准备阶段是正式为类变量分配内存并初始化的一个阶段。并且并不是按照代码中指示的值进行初始化,而是按照该变量所属类型的初始值进行初始化,比如int类型的初始值就是0.

解析

解析阶段就是虚拟机将符号引用转化为直接引用的过程。比如将com.jay.Student这个字符串符号引用,转化为真正的指向目标的直接引用。解析过程主要分几种情况:

1.类或者接口的解析

1)当这个类不是一个数组对象时,虚拟机就会将其全限定名传给对应的类加载器进行加载。当然这个过程中可能会触发其他类的加载动作

2)如果这个类是一个数组对象,并且数组元素类型是引用类型时,就按照第一步的方式加载该数组元素类,加载完成后,由虚拟机生成对应的数组对象。

3)如果上面的步骤没有发生异常,那么在虚拟机中已经存在一个有效的类或者接口了,接下来还要判断当前位置对该类是否有访问权限,若没有的话,会抛出异常

2.字段解析

字段解析之前,必须完成对其所属的类或者接口进行解析,然后才会执行字段解析,字段解析过程也分几种情况:

1)如果所属类本身就包含了与该字段名称相匹配的字段,那么直接返回该字段的直接引用

2)否则,若所属类实现了接口,再到接口中进行查找,若接口中包含了与字段名称匹配的字段,则返回该字段的直接引用

3)否则,若所属类有父类,则按照继承关系从下往上搜索其父值,若查找到了,则返回直接引用直接引用

4)若这都没找到的话,就抛出java.lang.NoSuchFieldError异常。

同时,如果查找过程中成功返回了引用,还需进行权限校验,若当前位置不具备访问权限,则回抛出异常

3.类方法解析

类方法解析和字段解析类似。在类方法解析之前,必须完成对其所属类的解析,然后才会执行类方法解析,类方法解析分几种情况:

1)若所属类包含与该类方法名称匹配的方法,则返回该方法的直接引用。

2)否则,若所属类有父类,则按照继承关系由下往上进行查找,若查找到了就返回直接引用

3)否则,若所属类有父接口,则在父接口上进行查找,若查找到了,说明该类是一个抽象类,此时会抛出异常

4)否则,1则类方法查找失败。

同样的,若查找到了,还需要进行权限检验

4.接口方法解析

1)在接口中查找是否含有与方法名称相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
2)否则,在父接口中递归查找,直到java.lang.Object类(查找范围会包括Object类)为止,看是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
3)否则,宣告方法查找失败,抛出java.lang.NoSuchMethodError异常。

初始化

类初始化是类加载的最后一步。在前面的流程中,几乎所有操作都是由虚拟机主导和控制的,到了类初始化阶段才开始真真正地执行类中的Java程序代码。

初始化阶段就是按照开发者指定的值进行初始化,也就是完成类中所有类变量的赋值以及静态代码块的执行。从另一个角度看,初始化阶段就是执行类构造器的()方法的过程。同样的clinit方法的执行也会有继承关系,也就是会先执行父类的clinit方法,然后执行子类的clinit方法。并且clinit方法只会执行一次

类加载器

对于任意一个类,都需要由它的类加载器和这个类本身一起来确立其在JVM中的唯一性。换句话来说,两个类是否相等的其中一个必要条件就是这两个类的类加载器必须是同一个。否则的话,即使是出于同一个Class文件的两个类,也不是相等的。

从开发者角度来看,类加载器分为三种:

启动类加载器(Bootstrap ClassLoader)

这个加载器负责加载存放在<JAVA_HOME>\lib中的库,并且这个加载器是在由JVM源码中的C/C++代码实现的,在Java程序中无法直接进行调用。像Object,System这样的类就由它进行加载。

需要注意的是,虚拟机是按照文件名来识别库的,所以名字不符合的库就算放在对应文件夹下也无法被加载

扩展类加载器(Extension ClassLoader)

这个加载器负责加载<JAVA_HOME>\lib\ext目录下的库,开发者可以直接使用该加载器

应用程序类加载库(Application ClassLoader)

复杂加载用户类路径(ClassPath)上所指定的库,开发者也可以直接使用。如果应用程序中没有自定义加载器,一般情况下就是使用这种加载器。

双亲委派模型

工作原理

当一个类加载器接收到了类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成,只有当父类加载器无法完成这个加载请求时,才会尝试自己去加载。

好处

保证Java程序的稳定运行。使得类加载器具备了一种带有优先级的层级关系。例如Object类,无论哪一个类加载器要加载这个类,最终都是委派到启动类加载器中进行加载,使得Object类在各种类加载环境下都是同一个类。

相反,如果没有双亲委派模型,要是用户自己定义了一个Object类,就会使得系统出现多个Object类,那么JVM中就一片混乱了。

发布了60 篇原创文章 · 获赞 7 · 访问量 3885

猜你喜欢

转载自blog.csdn.net/SCUTJAY/article/details/104419720