一个Java类怎么算被加载成功

在之前的一个问题中 (一次诊断 org.xerial.snappy.Snappy NoClassDefFoundError), 我们看到 Snappy 这个类并没有被 ClassLoader 加载成功, 原因是它要加载 native 代码, 可是由于 /tmp 目录挂载方式的问题, 导致 Snappy 的静态块部分抛出了 Error, 最终没有加载成功. 如果要本地重现的话, 可以通过单步调试(debug)的方式, 把它正常写入本地临时目录的文件中途删掉, 那么同样可以达到让它无法加载成功的目的.

这里困扰我的问题是, 虽然不能加载成功, 可是我们在 heap dump 中已经可以看到这个类, 只不过按照正常初始化应该完成初始化的 impl 字段在 heap dump 中是空值. 如下图:
heap.png

同时, 从 ClassLoader 的 parallelLockMap 中, 也能发现这个类已经在列表中.

正常情况下, 如果一个类的的静态块出错(跑出异常), 并且它有 try {} catch{} 并且 catch 部分捕获异常的话, 应该算正常执行完了静态块. 可是在这里例子中, 静态块跑了 Error, catch 部分没有捕获 Throwable 或 Error, 所以应该属于非正常退出静态块, 所以这里是该类未正常完成初始化.

所以在哪个测试的例子中, 第一次尝试使用 Snappy 类的时候, 并没有发生重新加载该类的行为, 直接跑出了异常:

Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class org.xerial.snappy.Snappy at SnappyTest.main(SnappyTest.java:13)

那么 JVM 到底在哪里记录某个类是不是被正常初始化了呢? 第二次是如何快速的就跑出了NoClassDefFoundError 呢?
在 openJDK 源码里 instanceKlass.cpp 中, 我们发现了对应的代码.
http://hg.openjdk.java.net/jdk/jdk/file/tip/src/hotspot/share/oops/instanceKlass.cpp#l1038

在第5步中, 它判断是不是在 error 状态 (is_in_error_state), 如果是在 error 状态, 那么就抛出了上面的异常.

在这个源代码 instanceKlass.hpp 中, 我们看到了有个初始化状态字段: _init_state, 它有几个状态:

enum ClassState {
    allocated,              // allocated (but not yet linked)
    loaded,                 // loaded and inserted in class hierarchy (but not linked yet)
    linked,                 // successfully linked/verified (but not initialized yet)
    being_initialized,      // currently running class initializer
    fully_initialized,      // initialized (successfull final state)
    initialization_error    // error happened during initialization
  };

所以, 可以看到, 它在 Load, Link, Initialize 每一步都会记录状态, 如果出错, 都会有相应的处理. 至于对于什么错误, 怎么处理, 还是要继续看 instanceKlass.cpp 的源码. 但是这部分的数据结构是不一定在 heap 里面, 所以不一定能在 heap 看到(我没有确认).

标签: none

添加新评论