美文网首页
虚拟机的类加载机制

虚拟机的类加载机制

作者: ACtong | 来源:发表于2020-05-10 16:53 被阅读0次

什么是类加载机制

  • Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

  • Java的类型加载、连接和初始化过程都是在运行期间完成的

类记载的时机

一个类型从被加载到虚拟机内存中开始,到卸载为止,它的整个生命周期会经历以下七个阶段,其中验证、准备、解析三个部分统称为连接。

1589528956533.png
  • 其中加载、验证、准备、初始化、卸载是确定的

  • 解析阶段则不一定:它在某些情况下可以在初始化阶段之后在开始,这是为了支持Java语言的运行时绑定特性。这不是按部就班的开始,这些阶段通常都是互相交叉地混合进行,会在一个阶段执行的过程中调用、激活另一个阶段。

Java虚拟机严格规定了只有六种情况必须对类进行“初始化”:

而加载、验证、准备自然而然在前进行,其中加载在《Java虚拟机规范》中没有进行强制性约束

  1. 遇到newgetstaticputstaticinvokestatic这四条字节码时,如果类型没有进行初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:

    • 使用new关键字实例化对象

    • 读取或者设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候

    • 调用一个类型的静态方法的时候

  2. 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行初始化,则需要触发其初始化。

  3. 当初始化类的时候,如果发现父类还没有进行过初始化,则需要先触发父类的初始化

  4. 当虚拟机启动时,用户需要指定一个执行主类(例如main()方法),虚拟机会优先初始化这个主类。

  5. 使用JDK 7新的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解 析结果为REF_getStaticREF_putStaticREF_invokeStaticREF_newInvokeSpecial四种类型的方法句 柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。

  6. 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

以上六种触发类型的场景,称为对一个类型进行主动引用。除此之外,所有引用类型的方式都不会触发初始化,称为被动引用

被动引用的例子
  1. 通过子类引用父类的静态字段,不会导致子类初始化
package org.fenixsoft.classloading; 
/** 
* 被动使用类字段演示一: 
* 通过子类引用父类的静态字段,不会导致子类初始化 
**/
public class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }

    public static int value = 123;
}
public class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init!");
    }
} 

public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(SubClass.value);
    }
}

执行结果:SuperClass init!
因为只有父类才会被初始化,子类是否要被初始化,这需要看虚拟机的具体实现时怎样的

  1. 通过数组定义来引用类,不会触发此类的初始化
  2. 常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
 public class ConstClass {
     static {
         System.out.println("ConstClass init!");
     }

     public static final String HELLOWORLD = "hello world";
 }
public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(ConstClass.HELLOWORLD);
    }
}

输出结果:hello world

没有输出“ConstClass init!”,因为ConstClass类的常量HELLOWORLD,但其实在编译阶段通过常量传播优化,已经将此常量的值“hello world”直接存储在NotInitialization类的常量池中,以后NotInitialization对常量 ConstClass.HELLOWORLD的引用,实际都被转化为NotInitialization类对自身常量池的引用了。

就是说NotInitialization的Class文件之中并没有ConstClass类的符号引用入口,这两个类编译成Class文件后就已不存在任何联系。

接口的加载过程与类加载过程稍有不同:

接口:

  • 接口中不能使 用“static{}”语句块,而类可以
  • 初始化的时候,并不要求父类接口完全初始化,只有真正用到父类接口的时候(如引用接口定义的常量),才会初始化

相关文章

网友评论

      本文标题:虚拟机的类加载机制

      本文链接:https://www.haomeiwen.com/subject/cbijnhtx.html