类声明周期
类从被加载到虚拟机内存开始,到卸载出内存为止,他生命周期包括了:
加载->验证->准备->解析->初始化->使用->卸载
什么情况下开始类加载第一阶段?
- 遇到new,getstatic,putstatic或者invokestatic这4条字节码指令时。如果类没有惊醒过初始化,则先触发其初始化。常见场景:new实例化对象,读取或者设置一个类的静态字段(被final修饰,已在编译期间把结果放入常量池的静态字段除外)的时候,以及调用静态方法。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需先触发初始化。
- 当初始化一个类,如果发现其父类还没有进行过初始化,则先出法其父类的初始化。
-
当虚拟机启动时,用户需要指定一个需要执行的主类,虚拟机会初始化这个主类。
这四种场景的行为成为对一个类的主动引用,除了以上所用引用类的方式都不会触发初始化,称为被动引用。
被动引用示例1:
package classloading;
public class SuperClass {
static {
System.out.println("classloading.SuperClass init!");
}
public static int value = 123;
}
package classloading;
public class SubClass extends SuperClass {
static {
System.out.println("subclass init!");
}
}
package classloading;
public class NotInitialization {
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
输出结果:
classloading.SuperClass init!
123
对于静态字段只有直接定义这个字段的类才会被初始化,因此通过子类引用父类定义的静态字段并不会触发子类初始化。至于是否要触发取决于虚拟机的具体实现。
被动引用示例2:
package classloading;
public class NotInitialization {
public static void main(String[] args) {
SuperClass[] sca=new SuperClass[10];
}
}
运行后发现并没有触发任何子类或者父类初始化。
被动引用示例3:
package classloading;
public class ConstantClass {
static {
System.out.println("ConstantClass init!!");
}
public static final String HELLOWORLD = "hello world!";
}
package classloading;
public class NotInitialization {
public static void main(String[] args) {
System.out.println(ConstantClass.HELLOWORLD);
}
}
运行后发现并未打印出ConstantClass init!!,这是因为java源码在编译阶段将次常量的值存储到了NotInitialization的常量池中,对常量ConstantClass.HELLOWORLD的引用实际上传化为了对自身常量池的引用了。
接口初始化:
接口与类初始化区别:类在书初始化的时候需要其父类全部初始化,但是接口在初始化时候,并不需要父接口全部完成初始化,只有真正使用到父类接口的时候才会初始化。