类(如果无特殊说明,本文中的“类”表示类和接口,下同)的初始化主要包括初始化的同步及执行其初始化方法<clinit>。
在以下几种情况下会触发类的初始化:
(1)执行JVM指令:new、getstatic、putstatic、invokestatic,会触发指令后的引用所指向类的初始化(若未初始化),即在java代码中体现为new一个对象,访问一个类的静态属性或静态方法;
(2)第一次调用2(REF_getStatic)、4(REF_putStatic)、6(REF_invokeStatic)类型的解析后的java.lang.invoke.MethodHandle实例,即在java代码中体现为访问一个类的静态属性或静态方法;
(3)调用某些反射方法,例如调用java.lang.Class类或调用java.lang.reflect包中的反射方法;
(4)子类的初始化会触发当前类的初始化;
(5)作为JVM启动的初始类(initial class)。
从线程角度,JVM是基于多线程设计的。所以对一个类的初始化也要考虑到初始化的同步问题。经过类的验证(Verification)和准备(Preparation)后的Class对象会处于以下几种初始化状态:
(1)该Class对象已经验证和准备,但还未初始化;
(2)该Class对象正在某一个线程中进行初始化;
(3)该Class对象已经初始化成功,处于待用状态;
(4)该Class对象处于初始化异常状态,可能之前已经尝试过初始化,但未成功。
任何一个类C都有唯一的初始化锁,假设为LC,假设有多个线程可能会进行类C的初始化,则类C的初始化过程如下(以下过程是从当前线程的角度来描述的):
(1)当前线程与多个线程竞争LC,当前线程等待直到获取到LC;
(2)若类C的Class对象表示其他的某一个线程T1正在进行类C的初始化,则当前线程释放LC,当前线程阻塞直到通知类C的初始化已经完成;
(3)若类C的Class对象表示当前线程正在进行类C的初始化,这一定是初始化的一个递归请求,则当前线程释放LC,初始化正常完成;
(4)若类C的Class对象表示类C处于已初始化状态,则当前线程释放LC,初始化正常完成;
(5)若类C的Class对象表示类C处于初始化异常状态,则停止初始化,当前线程释放LC,抛出NoClassDefFoundError异常;
(6)否则,记录当前线程正在进行类C的初始化,当前线程释放LC,然后按照ClassFile结构中的顺序,用静态常量ConstantValue属性中的值初始化静态常量(final static);
(7)若类C为class,有superclass SC,且SC还未初始化,则按照(1)—(6)递归对superclass进行初始化。若SC初始化异常,当前线程获取LC,标记类C的Class对象为初始化异常状态,通知其他等待线程,然后释放LC,抛出SC出现的异常,异常结束初始化;
(8)JVM通过询问类C的“the defining loader”,确定类C是否满足执行初始化方法<clinit>所需要的要求;
(9)执行类C的初始化方法<clinit>;
(10)若执行<clinit>成功,则当前线程获取LC,标记类C的Class对象为已初始化状态,通知其他等待线程,然后释放LC,初始化成功完成;
(11)若执行<clinit>抛出异常,假设异常为E:如果E不为Error类型,则创建ExceptionInInitializerError异常,E作为其参数,用创建的ExceptionInInitializerError异常进行后续操作;如果ExceptionInInitializerError因为OutOfMemoryError而创建失败,则用OutOfMemoryError异常进行后续的操作;
(12)获取LC,标记类C的Class对象为初始化异常状态,通知其他等待线程,然后释放LC,抛出上一步出现的异常,完成初始化过程。
附上官方《The Java® Virtual Machine Specification Java SE 7 Edition》链接:https://docs.oracle.com/javase/specs/jvms/se7/html/index.html。
链接来自:https://blog.csdn.net/Architect0719/article/details/50501746