我正在研究JVM规范文档和JLS,涉及Java中的类加载机制。
这是我的理解。
我发现令人困惑的是,在“解析和初始化”步骤中,如果引用了尚未从源代码加载的类,会发生什么?解析或初始化步骤是否会暂停其父类加载器进行类加载?
还是将加载,链接和初始化推迟到在运行时执行使用该引用的实际方法或代码之前?
答案 0 :(得分:1)
链接类或接口包括在必要时验证和准备该类或接口,其直接超类,其直接超接口及其元素类型(如果是数组类型)。在类或接口中解析符号引用是链接的可选部分。
因此,当不谈论类的直接超类型时,解析度是可选的,并且可能会延迟。
同一部分也包含
例如,Java虚拟机实现可以选择在使用某个类或接口时分别解析每个符号引用(“懒惰”或“最新”分辨率),或者在使用该类或接口时一次全部解析它们。正在验证(“急切”或“静态”分辨率)。这意味着在某些实现中,在初始化类或接口之后,解析过程可能会继续。
因此,该过程并不总是严格遵循您在问题中显示的图形。相反,解决方案可以看作是一个持续的过程。
在实践中,对于HotSpot JVM,某些类(如超类)必须立即得到解析。验证方法的代码时会解析其他类,该类恰好在为此JVM首次执行方法之前发生。
这不会影响方法代码引用的所有类,而是取决于实际的类型使用,例如HotSpot的验证程序将解析类型,以根据实际的类型层次结构检查分配的有效性,但如果将类型分配给自身或java.lang.Object
(即分配始终有效),则跳过此步骤。因此,某些类型只能在首次实际使用时才能解决,例如通过new
或由类型声明的static
方法实例化它们时。但这取决于代码的微妙方面。另请参见When is a Java Class loaded?或Does the JVM throw if an unused class is absent?
可能只有在反射数据中引用的类型,例如批注或调试属性,它们在运行期间从未解析,但可能在另一个引用中。
但是,由于这意味着将类型的解析推迟到实际需要的时间,因此也意味着就在此时,该操作将停止并等待先决条件类的此过程完成。因此,例如加载一个类始终意味着解析其直接超类,如果尚未加载则对其进行加载,这反过来又意味着解析该超类的超类,依此类推。因此它不会在完整的超类层次结构解决之前返回。
答案 1 :(得分:0)
JVMS在第5.3节中也有规定
如果Java虚拟机曾经在验证过程中尝试加载C类 (第5.4.1节)或解析(第5.4.3节)(但不初始化(第5.5节)),以及类加载器 用于启动C加载的对象抛出ClassNotFoundException的实例, 那么Java虚拟机必须抛出NoClassDefFoundError实例 其原因是ClassNotFoundException的实例。 (这里的一个微妙之处是执行了递归类加载以加载超类 作为决议的一部分(第5.3.5节,第3步)。因此,一个ClassNotFoundException 类加载器无法加载超类的结果必须包装在 NoClassDefFoundError。)
因此,在类加载的解析阶段确实存在递归。