加载、验证、准备、初始化、卸载这五个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段有可能在初始化阶段之后才开始,这是为了支持Java的动态绑定。
虚拟机并没有强制约束“加载”阶段的开始时机,而是交给虚拟机的具体实现来自由把握。但是对于初始化阶段,则严格规定有且只有五种情况必须对类进行初始化:
1、遇到new、getstatic、putstatic或invokestatic这四条指令时,如果类没有初始化,则必须先触发初始化动作。对应的场景分别是:
- 使用new关键字实例化对象
- 读取或设置一个类的静态字段(被final修饰的静态字段除外)
- 调用类的静态方法
2、使用java.lang.reflect包的方法对类进行反射的时候,如果类没有进行过初始化,则需要先触发其初始化。
3、当初始化一个类的时候,如果发现它的父类还没有初始化,则要先初始化父类。
4、当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个类。
5、当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有初始化,则要先触发其初始化。
子类引用父类静态字段.png这五种场景中的行为称为对一个类进行主动引用。除此之外,所有对类的引用方式都不会触发初始化,称为被动引用。
运行Subclass.value,只会输出"SuperClass init!"。对于静态字段,只有直接定义这个字段的类才会被初始化,因此本例中只有父类被初始化。
通过数组定义来引用类.png运行之后发现没有输出"SuperClass init!",说明没有触发SuperClass类的初始化阶段。
image.png运行之后没有输出"ConstClass init!",这是因为静态常量在编译期间,已经将ConstClass.HELLOWORLD的值放入NotInitialization类的常量池中,所以对HELLOWORLD的引用实际已经转化为NotInitialization类对自身常量池的引用,实际上两个类在编译完成之后就不存在任何联系了。
接口的加载过程与类加载过程稍微有点不同,接口也有初始化过程,但是与类有所区别的是前面提到的5种情况中的第三种:当一个类需要初始化时,需要保证所有的父类都被初始化,但是一个接口在初始化时,并不要求父接口都完成了初始化,只有真正使用到父接口的时候才会初始化(例如引用接口中的常量)。
网友评论