Java 类加载的解决阶段实际上从哪里开始?
我刚刚读完了Java虚拟机规范,关于类加载的部分让我感到困惑。就我一般的理解而言,在阅读了规范之后,我认为类的整体实例化由以下步骤组成,顺序如下:
创建/加载:类装入器定位表示类的字节流,可以是文件或网络流,也可以是实现类装入器要获取的任何内容。如果找不到任何类,则抛出 a。此时,已经发生了一些基本的验证,如果字节数组不表示 Java 类(例如,缺少幻数),或者如果正在运行的 JVM 实例不支持类版本,则抛出 a。
ClassNotFoundException
ClassFormatError
UnsupportedClassVersionError
-
链接:类被挂接到 JVM 中。如果出现问题,则会抛出 一个子类。链接由三个子步骤组成:
LinkageError
验证:确保字节流表示 Java 类,例如,没有正式错误的字节码,例如方法字节码的溢出操作数堆栈。如果类验证失败,则抛出 。
VerifyError
准备:JVM 为所有静态字段分配内存,并可能创建一个实例模板来加快实例创建速度。创建虚拟方法表。在此阶段不会引发特定于类加载的错误。(不过可能会抛出一个。
OutOfMemoryError
-
解决方法:现在以运行时常量池的形式加载到方法区域中的所有符号引用都将解析为此 JVM 加载的实际类型。如果符号引用可以解析,但导致定义冲突,则抛出 a。如果找不到引用的类,则抛出 a,它基本上包装了一个由类装入器尝试装入此被引用类所抛出的类。如果引用的类引用自身,则抛出 a。解决方案可以以两种类型之一进行,这取决于JVM的实现者
IncompatibleClassChangeError
NoClassDefFoundError
ClassNotFoundException
ClassCircularityError
Eager:现在已解析对其他字段、方法或类的所有符号引用。
惰性:符号引用的解析被推迟到第一次使用方法时。这可能会带来引用不存在的类永远不会引发错误,如果此引用永远不需要解析。
初始化:运行在类中定义为 Java 代码的类初始值设定项。如果异常是由此类初始值设定项引起的,则此异常将重新包装在 .
static
ExceptionInInitializerError
令我困惑的是上述类加载机制的解决阶段。为什么分辨率被定义为链接中的显式步骤,该步骤在准备后专门发生?在类加载阶段的描述中,已经提到
如果 C 具有任何直接超接口,则使用 §5.4.3.1 的算法解析从 C 到其直接超接口的符号引用。
在进行验证时,符号引用是否也未得到解决,因为验证被描述:
验证 (§4.10) 确保类或接口的二进制表示形式在结构上是正确的 (§4.9)。验证可能会导致加载其他类和接口 (§5.3),但不需要验证或准备它们。
我总是想着这张照片
资料来源:http://www.programcreek.com
我几乎在任何地方都看到过解释类加载。是否应该将解决方案视为所有阶段,创建/加载,验证,链接和初始化(因为解决方案可以懒惰地完成)的所有阶段的一部分。
目前,我认为将分辨率阶段从此图像中移除并声明为可以随时使用的一般过程是有意义的,因为在任何阶段都可能需要有关其他类的信息,因此需要加载此类类,这必然还需要对该类的符号引用的解析。从所示图片来看,分辨率似乎只发生在一连串单独事件中的特定点。
我怀疑,这种对决议是一个专门步骤的描述可能只是一个时代的遗产,在那个时代,决议从未懒惰地进行过,而是在所有剩余的象征性引用都得到解决的地方。
我想知道的是:今天的JVM中的分辨率是否应该像我描述的那样被理解?还是我错了,分辨率仍然可以被理解为固定时间线中的专用步骤,就像图像显示的那样?