JVM的类加载

一、基本类加载机制介绍

       大体引用一下《深入理解Java虚拟机》一书中对类加载的定义:虚拟机将描述类的二进制字节流(即Class文件)加载到内存中,并对其进行验证、准备、解析、初始化,最终

生成可以直接被虚拟机使用的Java类型(即已经校验合格且有clinit执行完clinit方法的Class对象),这就是JVM的类加载机制。

        一个好的定义,就应该是这样准确而简练的。下面先罗列一下类的生命周期:

        加载  ->  验证  ->  准备 ->  解析  ->  初始化  ->  使用  -> 卸载

        其中加载、验证、准备、初始化、卸载一定会严格按照上面的顺序进行。既然知道了类加载的大体流程,那么问题来了,什么时候会触发类的加载呢?

        虚拟机规范中并未作出明确的要求,但是会要求有且仅有下面五种情况发生的时候,会触发类的初始化(言外之意就是初始化之前的动作必须在此之前完成),这五种情况分别为:

1、遇到new、getStatic、putStatic、invokeStatic字节码指令时;

2、通过java.lang.reflect包对类进行反射调用的时候;

3、初始化子类时,如果父类没初始化,则先初始化父类(接口除外,接口初始化规则是调用哪个接口初始化哪个,不看继承关系);

4、虚拟机启动时,会自动初始化带有main方法的类;

5、使用动态语言支持时,如果一个MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄且对应的类未初始化时(恕BZ学识浅薄,目前还没

理解这种情况对应实际代码中的哪种情形,只能先放放,好在这东西一般也遇不到 (><) )。

    额外需要说明两点,这两点也经常在那种仿佛是考脑筋急转弯的编程题中遇到:一是在new一个引用类数组时不会触发这个类的初始化;二是对于常量的引用不会触发类初始化(static final)。

尤其是第二点要特别说明一下,如果A类中有一个常量叫VALUE,B类中引用了这个常量,那么在编译的时候,编译器会把这个VALUE直接放入B类的运行时常量池中供其后续使用,就是说在

编译之后,A类与B类已经没有任何交互了,所以也就不会触发初始化了。

二、类加载的各个过程简介

 1、加载

      类加载的第一个阶段叫加载,主要做的是三步:

1)、通过类的全限定名定位到对应的class文件;

2)、将二进制字节文件转化为方法区中的运行时数据;

3)、在内存中生成一个Class对象,作为方法区中访问运行时数据(即访问2)中数据)的入口。

此阶段自由度较高,我们可以自定义一个类加载器,加载class文件

2、验证

      验证主要包括四部分:文件格式校验、源数据校验(包括语义校验)、字节码校验、字符引用校验

3、准备

      在此阶段,虚拟机会为类变量(即static修饰的成员变量)分配内存并赋初始的默认值。例如一个类变量private static int a = 2,在此阶段中,会给赋初值,即a=0。实际的2这个值是在

初始化阶段赋予。而如果是static final类型的变量,在此阶段会直接赋终值。

4、解析

      将要加载的那个类的运行时常量池内的符号引用替换为直接引用

5、初始化

      初始化的过程就是执行类构造器<clinit>()方法的过程。此方法是在编译阶段由编译器收集类中的静态变量赋值动作、静态方法、静态块的语句合并而成的。

虚拟机会保证执行<clinit>()方法前会先执行父类的<clinit>方法,同样如果是接口则不用先执行父类的类构造器。

三、类加载器

    Java中一共定义了三种类加载器:启动类加载器  <--   扩展类加载器   <--  应用程序类加载器,继承关系如箭头所示,而我们自定的类加载器,都是以应用程序类加载器为父类。

    类加载器很重要,因为JVM中定义两个类是不是同一个类,只用两个判断条件:一个是类的全限定名是否相同,另一个是加载这个类的类加载器是否相同。如果类加载器不同,

即使类的全限定名一样,虚拟机一样会判定这是两个不同的类。

    Java中的类加载器加载类,采用双亲委派模型,即类加载器C要加载类A时,会先让父加载器去加载,一直往上传递,除非父类加载器中搜不到这个类,才会让子类去加载,并且

每个类只能被加载一次。这样做的好处就是可以限定核心类不会被替换篡改。比如一种常见的面试题,如果你重新编写了一个路径为java.lang.String的类,能否调用到这个自定的类

中的方法?在这里很明显答案是不会,因为JVM中已经加载了那个系统的java.lang.String类,当用应用程序类加载器加载我们自定义的String类时,由于双亲委派模型的存在,加载

任务会被委派给父类,一直往上传递,最后到启动类加载器那里,然后发现已经加载过一次这个全路径名的类了,不能重复加载,所以自定义的String类就无法被加载调用了。(有没

有发现这个剧情跟真假美猴王很相似,一直传递到佛祖那,才辨别出了真假 。。。)

      类加载就暂时到这里,书中还提到一些双亲委派模型被破坏的场景,由于了解的不够透彻,就先不乱发言了。学习之路,还是要坚持死磕啊,加油!

原文地址:https://www.cnblogs.com/zzq6032010/p/10127717.html