虚拟机类加载机制:类加载时机

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

类加载时机

类的生命周期

一个类从加载进内存到卸载出内存,经历图示的7个阶段:

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

其中,类的加载包括5个阶段:加载——>验证——>准备——>解析——>初始化。

在类加载的过程中,以下三个过程称为连接:验证——>准备——>解析。

因此JVM的类加载过程也可以概括为:加载——>连接——>初始化。

C/C++在运行前需要完成预处理、编译、汇编、链接;在Java中,类加载(加载、连接、初始化)是在程序运行期间完成的,这种策略虽然会使类加载时稍微增加一些性能开销,但是会为java应用程序提供高度的灵活性。java可以动态扩展语言特性就是依赖运行期间动态加载和动态链接这个特点实现的。比如,如果编写一个面向接口的程序,可以等到运行时再指定其具体实现类。

加载、验证、准备、初始化和卸载这五个阶段的顺序确定,而解析阶段不一定,在某些情况下可以在初始化阶段之后再开始。这是为了支持Java语言的运行时绑定。

类的加载时机

虚拟机并没有严格约束什么时候进行类加载,但是对于初始化的时机有严格的规定。在初始化之前,加载、验证、准备都应该开始。

初始化开始的时机:

  1. 遇到new,getstatic,putstatic,invokestatic四个字节码指令,如果类没有进行初始化,则需要先触发其初始化。
    • 使用new关键字实例化对象的时候
    • 读取或设置一个类的静态字段(不包括final以及编译期放入常量池的静态字段)
    • 调用一个类的静态方法
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
  3. 初始化一个类的时候,若其父类未初始化,先触发父类初始化,然后初始化本类。
  4. 当虚拟机启动时,用户指定一个需要执行的主类(main()方法的类),虚拟机会先初始化这个主类。
  5. 使用JDK1.7动态语言支持的时候的一些情况。

有且只有上述五种场景才会触发类进行初始化,称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。 以下为被动引用的几个例子

  1. 通过子类引用父类的静态字段,不会导致子类初始化
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 InitTest1{
    public static void main(String[] args){
        System.out.println(SubClass.value);
    }
}
复制代码

上述代码运行会输出:"SuperClass init!"以及123。对于静态字段,只有直接定义这个字段的类才会被初始化。 2. 通过数字定义引用类,不会触发此类的初始化

public class InitTest2{
    public static void main(String[] args){
        SuperClass[] sca=new SuperClass[10];
    }
}
复制代码

上述代码没有任何输出。 3. 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用定义常量的类,因此不会触发定义常量的类的初始化

class ConstClass{
    static{
        System.out.println("ConstClass init");
    }
    public static final String HELLOWORLD="hello world";
}

public class InitTest3 {
    public static void main(String[] args){
        System.out.println(ConstClass.HELLOWORLD);
    }
}
复制代码

上述代码只输出了“hello world”。原因是在编译阶段,已经将常量的值存储到了InitTest3类的常量池中,在该类中对ConstClass.HELLOWORLD的引用实际上被转化为InitTest3类自身常量池的引用。

接口的初始化

对于接口的初始化过程,与类的区别主要在于第三种情形,当一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口是,才会初始化。

参考资料

猜你喜欢

转载自juejin.im/post/5c089c5ff265da611510970c
今日推荐