虚拟机类加载机制之类加载时机(JVM学习笔记)

时间:2023-01-02 23:13:19

类声明周期

类从被加载到虚拟机内存开始,到卸载出内存为止,他生命周期包括了:

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

什么情况下开始类加载第一阶段?

  1. 遇到new,getstatic,putstatic或者invokestatic这4条字节码指令时。如果类没有惊醒过初始化,则先触发其初始化。常见场景:new实例化对象,读取或者设置一个类的静态字段(被final修饰,已在编译期间把结果放入常量池的静态字段除外)的时候,以及调用静态方法。
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需先触发初始化。
  3. 当初始化一个类,如果发现其父类还没有进行过初始化,则先出法其父类的初始化。
  4. 当虚拟机启动时,用户需要指定一个需要执行的主类,虚拟机会初始化这个主类。

    这四种场景的行为成为对一个类的主动引用,除了以上所用引用类的方式都不会触发初始化,称为被动引用。

被动引用示例1:

package classloading;

public class SuperClass {
    static {
        System.out.println("classloading.SuperClass init!");
    }

    public static int value = 123;
}
package classloading;

public class SubClass extends SuperClass {
    static {
        System.out.println("subclass init!");
    }
}
package classloading;
public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(SubClass.value);
    }
}

输出结果:

classloading.SuperClass init!

123

对于静态字段只有直接定义这个字段的类才会被初始化,因此通过子类引用父类定义的静态字段并不会触发子类初始化。至于是否要触发取决于虚拟机的具体实现。

被动引用示例2:

package classloading;
public class NotInitialization {
    public static void main(String[] args) {
        SuperClass[] sca=new SuperClass[10];
    }
}

运行后发现并没有触发任何子类或者父类初始化。

被动引用示例3:

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

运行后发现并未打印出ConstantClass init!!,这是因为java源码在编译阶段将次常量的值存储到了NotInitialization的常量池中,对常量ConstantClass.HELLOWORLD的引用实际上传化为了对自身常量池的引用了。

接口初始化:

接口与类初始化区别:类在书初始化的时候需要其父类全部初始化,但是接口在初始化时候,并不需要父接口全部完成初始化,只有真正使用到父类接口的时候才会初始化。