目录
- JVM实战-JVM之类加载时机
- 1 主动引用
- 2 被动引用
JVM实战-JVM之类加载时机
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。
关于在什么情况下需要开始类加载过程的第一个阶段“加载”,《Java虚拟机规范》中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行“初始化”(初始化必然要先加载)。
Java程序对类的使用分为主动引用和被动引用,针对上述六种情况,对于这六种会触发类型进行初始化的场景,《Java虚拟机规范》中使用了一个非常强烈的限定语——“有且只有”。
1 主动引用
主动引用的六种情况:
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:
- 使用new关键字实例化对象的时候。
- 访问某个类或者接口的静态变量,或者对该静态变量进行赋值(被final修饰的静态变量、已在编译期间进入常量池的静态字段除外)。
- 调用一个类型的静态方法的时候
- 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。
- 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
- 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStaticREF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
- 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
/**
* FileName: ClassInitTest
* Author: lxg
*/
public class ClassInitTest {
public static void main(String[] args) {
// 1. 创建实例
InitTest1 it1 = new InitTest1();
// 2. 访问静态属性
// System.out.println(InitTest1.n);
// 3. 调用静态方法
// InitTest1.method();
// 4. 反射
// Class cls = Class.forName("classloder.initialization.InitTest1");
// 5. 初始化子类
// InitTest2 it2 = new InitTest2();
// 6. 启动类触发初始化,执行main()触发
// 7. 跳过
// 8. 当一个接口中的默认方法(被default关键字修饰的接口方法)初始化时,IfTestImpl初始化时会触发InitTest1 it1 = new InitTest1(),证明IfTest接口初始化
// IfTest ifTest = new IfTestImpl();
}
}
// 类初始化会执行初始化类静态属性
class InitTest1 {
static {
System.out.println("InitTest1初始化...");
}
public static int n = 10; // 静态变量
public static void method() { // 静态方法
n = 30;
}
}
class InitTest2 extends InitTest1 {
static {
System.out.println("InitTest2初始化...");
}
}
// 接口,验证第8条
interface IfTest {
InitTest1 it1 = new InitTest1();
default void method() {
}
}
class IfTestImpl implements IfTest {
static {
System.out.println("IfTestImpl初始化...");
}
}
2 被动引用
除了上述主动引用的六种情况会触发类的初始化,其他的引用都不会触发初始化,被称为被动引用,以下是被动引用的其中几种情况举例:
- 通过子类的引用父类的静态属性,不会导致子类初始化。
- 通过定义数组引用类,不会导致该类初始化。
- 访问类中final修饰的静态变量(常量在编译期间会存入常量池,本质上并没有直接引用到定义该常量的类,所以不会触发该类的初始化)。
public class ClassInitTest2 {
public static void main(String[] args) {
System.out.println(SubClass.m);
}
}
/**
* 被动使用类字段
* 通过子类引用父类的静态字段,不会导致子类的初始化
*/
class SuperClass{
static {
System.out.println("Super class init");
}
public final static int m = 20;
public static int n = 10;
}
class SubClass extends SuperClass{
static {
System.out.println("SubClass init");
}
}