「JVM 类加载子系统」系列文章
在类生命周期的七个阶段中,加载、验证、准备、初始化和卸载五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段不一定
解析阶段是 Java 虚拟机将常量池内的符号引用替换成直接引用的过程,所以这个过程可能在加载阶段完成,也可能在运行期间完成,这是为了支持 Java 运行时绑定
《Java 虚拟机规范》中并没有强制约束什么情况下必须开始类加载过程的第一个阶段「加载」;但是对于初始化阶段,严格规定了有且只有六种情况必须立即对类进行「初始化」
new
、getstatic
、putstatic
、invokestatic
四条字节码指令时,如果类型没有进行初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型 Java 代码场景有:new
关键字实例化对象的时候final
修饰的静态变量,已在编译期把结果放入常量池,读取该字段是不会引起初始化)java.lang.reflect
包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化java.lang.invoke.MethodHandle
实例最后的解析结果为REF_getStatic
、REF_putStatic
、REF_invokeStatic
、REF_newInvokeSpecial
四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化这六种场景中的行为称为对一个类型进行主动引用;除此之外,所有引用类型的方式都不会触发初始化,称为被动引用
下面是四种被动引用的例子:
// ---------------------- 例子 1 ----------------------
// 对于静态字段,只有直接定义字段的类才会被初始化
// 通过子类引用父类中定义的静态字段,只会触发父类的初始化,而不会触发子类的初始化
public class SuperClass {
static {
System.out.println("Super Class 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);
}
}
// result
// Super Class init!
// 123
// ---------------------- 例子 2 ----------------------
// 通过数组定义来引用类,不会触发此类的初始化
public class NotInitialization {
public static void main(String[] args) {
SuperClass[] arr = new SuperClass[10];
}
}
// ---------------------- 例子 3 ----------------------
// 调用静态常量,不会触发此类的初始化
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);
}
}
// result
// hello world
// ---------------------- 例子 4 ----------------------
public static void main(String[] args) throws ClassNotFoundException {
// 不会引起类的初始化
Class<MyObj> myObjClass = MyObj.class;
// 会引起类的初始化
Class<?> aClass = Class.forName("com.lfool.myself.MyObj");
}