欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

全面解析JVM加载中初始化的时机

程序员文章站 2022-06-23 22:58:51
JVM类加载过程分为几个阶段,分别是加载、验证、准备、解析和初始化。初始化阶段都是在什么时候触发的呢?本文给你带来详细解析。 ......

jvm类加载过程

jvm类加载过程分为几个阶段,分别是加载验证准备解析初始化加载是把二进制字节码载入内存,验证是校验字节流中包含的信息是否符合当要求,准备是为静态变量分配内存并设置静态变量初始值,解析是把常量池内的符号引用替换为直接引用,初始化是执行所有静态变量的赋值动作和静态语句块中的语句。更多详尽分析请阅读之前的文章《jvm的类加载机制全面解析》,这里不再赘述了。

欢迎关注微信公众号:万猫学社,每周一分享java技术干货。

类初始化的时机

对于我们开发人员,我认为应该具体了解一下初始化阶段什么时候在开始。jvm规范对此做了严格规范,有且只有以下5种情况必须对类进行初始化:

  1. 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类没有被初始化过,就需要先进行初始化。对于字节码指令不了解的同学,可能就是一脸蒙圈了。我们来说人话,就是:使用new关键字实例化对象的时候、读取和设置一个类的静态字段(不被final修饰的)和调用一个类的静态方法的时候。这样说更容易被理解一些。

  2. 使用java.lang.reflect包中的方法对类进行反射调用的时候,如果类没有被初始化过,就需要先进行初始化。

  3. 当初始化一个类的时候,如果发现它的父类还没有被初始化过,就需要先初始化它的父类。

  4. jvm会先初始化要执行的主类,也是包含main()方法的那个类。

  5. 当使用jdk 1.7的动态语言支持时,如果java.lang.invoke.methodhandle实例最后的解析结果是ref_getstatic(使用methodhandle读取类的静态字段)、ref_putstatic(使用methodhandle设置类的静态字段)、ref_invokestatic(使用methodhandle调用类的静态方法)的方法句柄时,如果这个方法句柄没有被初始化过,就需要先进行初始化。

欢迎关注微信公众号:万猫学社,每周一分享java技术干货。

被动引用

刚刚提到的5种情况,都会触发初始化,这些行为为称为对一个类的主动引用。除了这些以外,所有引用类的方式都不会触发初始化,被为被动引用。为了更好的理解,下面举几个被动引用的例子。

通过子类引用父类的静态变量

public class superclass {
    static {
        system.out.println("父类正在初始化");
    }

    public static string name = "万猫学社";
}
public class subclass extends superclass {
    static {
        system.out.println("子类正在初始化");
    }
}
public class onemorestudy {
    public static void main(string[] args) {
        system.out.println(subclass.name);
    }
}

对于静态变量,只有直接定义这个变量的类才会被初始化,通过子类引用父类中定义的静态变量,只会触发父类的初始化而不会触发子类的初始化,运行的结果是:

父类正在初始化
万猫学社

结果中并没有“子类正在初始化”。

欢迎关注微信公众号:万猫学社,每周一分享java技术干货。

通过数组定义来引用类

public class onemorestudy {
    public static void main(string[] args) {
        superclass[] arrays = new superclass[10];
        system.out.println("数组元素个数:" + arrays.length);
    }
}

这段代码中使用之前的superclass类,定义了一个superclass类的一维数组,运行后的结果是:

数组元素个数:10

结果中并没有“父类正在初始化”,说明并没有触发superclass类的初始化。实际上,有一个名为“[lsuperclass”的类被初始化了,它是由jvm自动生成的、直接继承于java.lang.object,创建动作由字节码指令newarray触发。

欢迎关注微信公众号:万猫学社,每周一分享java技术干货。

常量

public class constclass {
    static {
        system.out.println("有常量的类正在初始化");
    }

    public static final string name = "万猫学社";
}
public class onemorestudy {
    public static void main(string[] args) {
        system.out.println(constclass.name);
    }
}

常量在编译阶段会存入调用类的常量池中,本质没有直接引用到定义的常量的类,不会触发定义常量的类的初始化,所以运行的结果是:

万猫学社

结果中并没有“有常量的类正在初始化”。

欢迎关注微信公众号:万猫学社,每周一分享java技术干货。

接口初始化的时机

接口也有初始化过程,和类是一致的。不过接口中不能使用“static{}”语句块,但编译器仍然会为接口生成“clinit()”类构造器,用于初始化接口中所定义的成员变量。

接口初始化的时机,基本和之前提到的类的5种情况基本一致,唯一不一样的是第3种情况:在一个类被初始化时,它的父类也必须被初始化,但是一个接口被初始化时,它的父接口并不要求被初始化。只有在真正使用到父接口时才会被初始化,比如:引用父接口中定义的常量。

欢迎关注微信公众号:万猫学社,每周一分享java技术干货。

结语

这次主要分享了类在什么时候被初始化,共有5种情况。除了这种5种情况的引用叫做被动引用,同时举了3个被动引用的例子。同时,也提到初始化接口和类有什么不同。

欢迎关注微信公众号:万猫学社,每周一分享java技术干货。