引言
虚拟机把描述类的数据即Class文件加载到内存中,并对数据进行验证、转换解析和初始化,最终形成可以直接被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
类的生命周期
类初始化的时机
第一个阶段加载是由虚拟机控制,对于初始化阶段,虚拟机严格要求有且只有5种情况触发初始化。
1)遇到new、putstatic、getstatic、invokestatic指令时触发,也就是使用new关键字实例化对象时、读取和设置类的静态字段(被final static修饰存入常量池的除外)、调用类的静态方法。
2)使用java.lang.reflect包的方法对类进行反射调用时触发。
3)初始化一个类时发现其父类未初始化时会初始化其父类。
4)虚拟机启动时,用户需指定一个主类(含有main方法的类),首先初始化该主类。
5)使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getstatic、REF_petstatic、REF_invokestatic的方法句柄,并且该方法句柄对应的类没有初始化时触发。
上述五种情况属于主动引用,还有被动引用不会触发初始化。
被动引用案例1
package com.wjz.demo;
public class SuperClass {
public static int i = 1;
static {
System.out.println("SuperClass init");
}
}
package com.wjz.demo;
public class SubClass extends SuperClass {
static {
System.out.println("SubClass init");
}
}
package com.wjz.demo;
public class NotInitialization {
/**
* 非主动使用类字段
*/
public static void main(String[] args) {
System.out.println(SubClass.i);
}
}
输出结果
SuperClass init
1
对于静态字段只有直接定义该字段的类会被初始化,通过子类引用父类的静态字段只会初始化父类。
被动引用案例2
package com.wjz.demo;
public class NotInitialization {
/**
* 通过数组定义来引用类,不会初始化该类
*/
public static void main(String[] args) {
SuperClass[] scs = new SuperClass[10];
}
}
运行结果是什么都没有,说明没有初始化SuperClass类,但是虚拟机触发了另外一个名为“[Lcom.wjz.demo.SuperClass”的类的初始化阶段,这是由虚拟机自动生成的,创建动作由newarray指令触发。
被动引用案例3
package com.wjz.demo;
public class ConstantClass {
public final static String val = "Hello";
static {
System.out.println("ConstantClass init");
}
}
package com.wjz.demo;
public class NotInitialization {
/**
* 常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,不会初始化定义常量的类
*/
public static void main(String[] args) {
System.out.println(ConstantClass.val);
}
}
运行结果只输出了“Hello”,这是因为在编译阶段通过常量传播优化,“Hello”存储到了NotInitialization类的常量池中,之后对常量ConstantClass.val的引用实际都转化为了NotInitialization对自身常量池的引用,NotInitialization的Class文件中没有ConstantClass类的符号引用入口。