关于 NoClassDefFoundError, 稍有经验的开发应该都遇到过,比如下面这个异常:
1 | java.lang.NoClassDefFoundError: org/apache/commons/lang/exception/NestableRuntimeException |
ClassNotFoundException 是引起 NoClassDefFoundError 的最常见原因,常见于实际需要使用的依赖版本与应用依赖不一致,这个问题非常常见,但是估计大家都没注意 ClassNotFoundException 为什么会被转换为 NoClassDefFoundError, 在一次线上问题的排查过程中笔者查询了 JLS 中的类的详细初始化顺序 Detailed Initialization Procedure, 在此摘抄一段最核心的部分:
1 | 1. Synchronize on the initialization lock, LC, for C. This involves waiting until the current thread can acquire LC. |
关于为什么笔者会查询到类详细的加载顺序文档呢,这个还要从另一个问题说起,我们是否应该 catch Throwable? 根据 Java 文档,Throwable 是所有异常和错误的超类,且 Error 一般代表不可恢复的错误,Exception 一般表示可恢复的异常,根据 Is It a Bad Practice to Catch Throwable? 类似的文章都不建议对 Throwable 进行 catch 然后处理,因为 Throwable 包含了不可恢复的 Error, 如 OutOfMemoryError, 所以笔者经常写的异常捕获代码为:
1 | try { |
直到业务方反馈有一块业务逻辑没有生效,该业务逻辑采用消息队列消费实现,笔者查询了业务方反馈时间段的日志,没有发现任何异常日志,且查询到消息消费的状态为失败,后经过反复排查,确认为 NoClassDefFoundError 导致,正好业务逻辑中的异常日志记录代码为 catch Exception 实现,从而使 NoClassDefFoundError 未被捕捉从而没有被日志记录,但是为什么会出现 NoClassDefFoundError 呢?出问题的类是业务方自己编写的类,并不存在依赖版本不一致出现 ClassNotFoundException 导致 NoClassDefFoundError 的问题,后面经过排查,原来是 static 代码块中产生了异常导致,为何 static 代码块中的异常会导致 NoClassDefFoundError 呢,于是查询到了以上的 JLS 中的类加载文档,static 代码块中的运行时异常导致经历了上面 11 -> 12 -> 5 的流程,示例代码:
1 | package me.tianshuang; |
此时,Test.main() 会抛出错误 ExceptionInInitializerError, 即上面的第 11 步:
1 | Exception in thread "main" java.lang.ExceptionInInitializerError |
而如果我们未对 Error 进行处理,再调用 LogisticsUtil.print 方法,此时会触发第 5 步:
1 | package me.tianshuang; |
异常栈帧如下:
1 | Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class me.tianshuang.LogisticsUtil |
现在又回到了原点,是否该 catch Throwable?
相关文档参考:
Why catch Exceptions in Java, when you can catch Throwables?
Catching java.lang.OutOfMemoryError?
When Does Java Throw the ExceptionInInitializerError?
Why NoClassDefFoundError caused by static field initialization failure?