前言
在使用java开发的过程中时常会碰到以上三个错误,其中NoClassDefFoundError、NoSuchMethodError两个error遭遇得会多一些。本文会简单分析三个异常发生的原因,并给出排查思路和相关工具。
定义
NoClassDefFoundError
Thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the new expression) and no definition of the class could be found.
The searched-for class definition existed when the currently executing class was compiled, but the definition can no longer be found.
ClassNotFoundException
Thrown when an application tries to load in a class through its string name using: The forName method in class Class. The findSystemClass method in class ClassLoader . The loadClass method in class ClassLoader.
这两者都发生在运行期‘找不到需要的类’,但是需要注意的是ClassNotFoundException出现主要是由于在运行期尝试根据类名加载(通过Class.forName、ClassLoader.findSystemClass、ClassLoader.loadClass),但是找不到需要加载的类。ClassNotFoundException的异常场景有限,所以通常遭遇得比较少,如果遭遇了只要查找classpath是是否真的存在对应的类即可。平时遭遇的更常见的与‘找不到类’相关的错误是NoClassDefFoundError
NoClassDefFoundError
这个错误发生的场景就比较多了,较为常见的有:
- 运行期真真找不到对应的类
例如A.jar的A.class依赖了B.jar的B.class,但是B.jar中由于某些原因并不存在B.class,此时就会抛出NoClassDefFoundError
- 加载的类初始化错误
加载的class在初始化(loaded->linked->initialized)过程中出错了,初始化过程不可逆,以后凡是使用该class的地方都会抛出NoClassDefFoundError。这个错误通常是发生在clinit方法中,具体可能是静态变量,静态代码块。可参考寒泉子大大写的不可逆的类初始化过程 。通常错误堆栈表现为:
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class xxxx
...
如果你运气好的话,可能在该错误的上方看到java.lang.ExceptionInInitializerError的错误堆栈从而找到对应出错的地方;如果运气不好的话,该异常可能被吞掉,如果出问题的类你有权限修改的话,你可以显式catch异常打印日志,如果是二方包或者三方包的话...可能要使用比较tricky的手段了。
NoSuchMethodError
这个比较好理解了,在运行时找不到对应的类的对应方法,通常由于jar包依赖冲突导致。
排查思路与工具
ClassNotFoundException
由于是出现在运行期,我们要确定是否真的不存在该类,推荐使用脚本扫描war包(重点找lib包),此扫描脚本 来自于阿里的哲良大大。
sh /home/admin/devops/find-in-jars.sh -d . 'xxx.class'
如果没有找到,则加入对应的相关jar包即可
NoClassDefFoundError
首先看对应的错误堆栈,如果错误堆栈类似于:
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class xxxx
...
则表明是类的初始化过程发生了不可逆错误,参考上文所说的解决方案。
如果堆栈类似于:
java.lang.NoClassDefFoundError: com/taobao/pamirs/base/log/ErrorMonitorLog
没有出现‘ Could not initialize class’等关键字,此时可参考ClassNotFoundException的排查方法,使用扫描脚本确定war包是否真的存在该类,不存在的话则添加相关的jar包。
NoSuchMethodError
这个错误多半是由于jar包依赖冲突导致,依赖冲突是一个非常DT的问题。NoSuchMethodError出现是多半是存在两个同fully qualified name的class,刚好优先加载到了少了方法的那个。更为DT的是可能在不同机器上表现不一致(首先加载哪个class顺序不确定),‘对于classloader而言,找文件的过程取决于文件系统返回的顺序,简单的说,在linux上取决于两个inode的顺序’。
上面有些扯远了...解决这个问题先找到错误堆栈:
NoSuchMethodError: com.foo.SomeService.doSmth()Z
通过扫描脚本扫描lib包,看是否存在两个同fully qualified name的class出现在两个不同的jar版,如果存在,则排除其中一个版本的jar包。
但是需要注意的是,也有一种可能性是扫描com.foo.SomeService 会发现只存在一个对应class name的class文件。这时候需要调整下思路了,很可能是其父类类加载冲突了。比如说曾经遭遇过org.apache.log4j.DailyRollingFileAppender.setAppend NoSuchMethodError,排查半天后发现是其父类org.apache.log4j.FileAppender类加载冲突了。
Maven依赖树
由于集团多使用maven,由于依赖冲突会导致如上所述的NoSuchMethodError错误,在遭遇此类问题时通常会需要打印出应用的maven依赖树,通过我们会使用以下几种方式:
- maven命令
mvn dependency:tree -Dverbose > tree.txt
当然加不加verbose也行
- 使用maven helper
使用‘Dependency Analyzer’面板
- 不知道为啥我的IDEA从某个版本开始就没法使用maven helper了,参考了其部分代码,自己写了个简易插件来生成maven的依赖树。使用方法是,在对应的pom文件右键选择maven菜单中的tree子菜单即可生成tree.txt文件,这个操作等同于在pom文件所在的子module里mvn dependency:tree -Dverbose > tree.txt。此插件源码在此,里面也附上了可安装的jar,下载安装即可使用。
总结
本文简述了ClassNotFoundException,NoClassDefFoundError,NoSuchMethodError的发生场景,给出了相关的排查思路与排查工具。
参考文献
哲良的oldratlee的useful-scripts
ClassNotFoundException vs. NoClassDefFoundError
PreCheck 依赖树打印插件