类加载的时机
类加载的生命周期为: 加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三个阶段统称为连接。其中加载与连接时交叉执行的。
类必须初始化的六种情况
- 遇到new、getstatic、putstatic、或者invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需先触发其初始化阶段。
- 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需先触发其初始化阶段。
- 当初始化类的时候,其父类还没有初始化,则需先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
- 当使用JDK7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需先触发其初始化。
- 当一个接口中定义了JDK8新加入的默认方法(default关键字修饰的接口方法)时,如果有这个接口的实现类进行初始化的场景,那该接口要在其之前被初始化。
注意: ① 《Java虚拟机规范》中使用了非常强烈的限定语——"有且仅有"。② 接口与类真正取别的是第三种初始化场景: 接口在初始化时,并不要求其父接口全部完成初始化,只有真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。
被动引用示例
示例一
package com.chinda.init;
/**
* 被动使用类字段演示一
* 通过字类引用父类的静态字段, 不会导致子类初始化
*
* @author Wang Chinda
* @date 2020/3/16
* @see
* @since 1.0
*/
public class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
package com.chinda.init;
/**
* @author Wang Chinda
* @date 2020/3/16
* @see
* @since 1.0
*/
public class SubClass extends SuperClass {
static {
System.out.println("SubClass init!");
}
}
/**
* 控制台打印输出
* SuperClass init!
* 123
* <p>
* 通过字类引用父类的静态字段, 不会导致子类初始化
*/
@Test
public void test1() {
System.out.println(SubClass.value);
}
示例二
/**
* 通过数组定义来引用类, 不会触发此类的初始化
*/
@Test
public void test2() {
SubClass[] sca = new SubClass[10];
}
示例三
package com.chinda.init;
/**
* @author Wang Chinda
* @date 2020/3/16
* @see
* @since 1.0
*/
public class ConstClass {
static {
System.out.println("ConstClass init");
}
public static final String HELLOWORLD = "helloworld";
}
/**
* 常量在编译阶段会存入调用类的常量池中, 本质上没有直接引用到定义常量的类, 因此不会触发定义常量的类的初始化。
*/
@Test
public void test3() {
System.out.println(ConstClass.HELLOWORLD);
}