类加载的时机
类在虚拟机内存中的生命周期分为七个阶段:
加载(Loading):
验证(Verification)
准备(Preparation)
解析(Resolution)
初始化(Initialization)
使用(Using)
卸载(Unloading)
加载、验证、准备、初始化、卸载的顺序是确定的,而解析在某些情况下可以在初始化阶段之后再开始,这是为了支持java语言的运行时绑定(动态绑定和晚期绑定)。
加载的时机:虚拟机规范并没有强制约束在什么情况下需要开始加载,这点交给虚拟机的具体实现自由把握。
初始化的时机:虚拟机规范严格规定有且只有一下四种情况下类如果没有进行过初始化则必须立即对类进行初始化:
(1)使用new关键字实例化对象的时候(new),读取或设置一个类的静态字段(getstatic putstatic 被final修饰、已在编译器把结果放入常量池的静态字段除外),调用一个类的静态方法的时候(invokestatic)
(2)反射
(3)父类未初始化而子类在进行初始化时
(4)启动虚拟机时。包含main方法的那个主类。
以上四种情况称为主动引用,除此之外的所有引用都不会出发初始化,称为被动引用。例如:
1.对于静态字段的引用只有直接顶一个这个字段的类才会被初始化。例如通过子类来引用父类的静态字段只会触发父类的初始化而不会触发子类的初始化。此种引用是否触发子类的加载和验证虚拟机规范则没有明确规定。可以通过-XX:+TraceClassLoading查看。
子类来引用父类的静态字段代码示例:
public class SuperClass{
static {
System.out.println("main init");
}
public static void main(String[] args) {
System.out.println(Sub.value);
}
}
class Sub extends Super{
static {
System.out.println("sub init");
}
}
class Super{
static {
System.out.println("super init");
}
public static int value = 10;
}
输出:
2,定义一个对象数组。
定义数组对象示例:
public class SuperClass{
static {
System.out.println("main init");
}
public static void main(String[] args) {
Super[] s = new Super[10];
}
}
class Sub extends Super{
static {
System.out.println("sub init");
}
}
class Super{
static {
System.out.println("super init");
}
public static final int value = 10;
}
输出结果:
3.引用一个常量,常量值在编译阶段已经存储到了引用它的那个类的常量池中,此类的Class文件中并没有定义常量的那个类的符号引用入口,两者在编译成Class之后就不存在任何联系。
引用一个常量示例:
public class SuperClass{
static {
System.out.println("main init");
}
public static void main(String[] args) {
System.out.println(Sub.value);
}
}
class Sub extends Super{
static {
System.out.println("sub init");
}
}
class Super{
static {
System.out.println("super init");
}
public static final int value = 10;
}
输出结果:
接口的初始化与类的大致相同,只有第三点不一样,子接口初始化时并不要求其父接口全部初始化,只有在真正使用到父借口的时候(如引用接口中定义的常量)才会初始化。
参考资料:《深入理解java虚拟机》--周志明