最近因为项目的原因,发现一个运行时错误:NoClassDefFoundError,对于这个常见的java Error进行一下总结。同时区分一下NoClassDefFoundError与ClassNotFoundException。
ClassNotFoundException
下面是一个ClassNotFoundException出现的场景,当使用JDBC去连接数据库的时候,一般会使用Class.forName()的方式去加载JDBC的驱动,如果没有将驱动放到应用的classpath下,那么会导致运行时找不到类,所以运行Class.forName()会抛出ClassNotFoundException:
public class MainClass {
public static void main(String[] args) {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Log
java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at com.ebay.app.SampleResource.main(SampleResource.java:43)
NoClassDefFoundError
NoClassDefFoundError是一个运行时异常,造成这个异常的原因,是在运行过程中,JVM无法找到运行时需要的class文件。
为什么在编译时能够通过,但是运行时却无法找到对应的class类。下面举一个例子。
有三个类,DependencyA(A),DependencyB(B),DependencyC(C)。这三个类是不同项目下的。
其中依赖关系:
-
A依赖B,并使用B中的打印方法:
-
B中依赖C,并使用了C中的方法:
pom.xml 如下:
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>C</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
-
C只是进行一个简单的输出:
NoClassDefFoundError
当执行A中的main方法时,可以看到,正常打印了A中的语句和B中的语句,但是在继续运行C的方法的时候,出现异常java.lang.NoClassDefFoundError: com/demo/c/DependencyC
-
Log
- Pom
由于demo中的代码使用maven进行依赖管理,需要考虑依赖问题。当查看pom文件配置后发现,程序A中只引入了B所在的jar包,但是对于B中需要的C类的jar包没有做依赖声明。
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>B</artifactId>
</dependency>
</dependencies>
- 分析
A的做法是标准的Maven的做法,但是由于B对C的依赖是provided。而对于scope=provided的情况,maven 不会管理C,因此在class path 就没有C的jar。最常见的这类artifact是servlet-api.jar。
当JVM在进行类加载的过程中,A依赖B,加载B的时候,从当前的依赖的Jar中找到对应的class文件,B中打印语句可以正常执行。当B依赖C,JVM开始加载C的过程中发现,无法在当前class path中找到对应的class文件,所以在这种情况下,就会报出NoClassDefFoundError。
其它出现NoClassDefFoundError的形式
其实还有更简单的形式,例如编译通过后,手动删除引用的某个class文件;修改引用的jar包名称。归根结底,NoClassDefFoundError出现的原因都是因为类加载过程中没有办法找到对应的class文件。
在源码中也明确写道,当JVM或类加载器尝试加载一个类时,这个类对应的class文件没有找到时,JVM会报出当前的错误。
/**
* Thrown if the Java Virtual Machine or a <code>ClassLoader</code> 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 <code>new</code> expression)
* and no definition of the class could be found.
* <p>
* The searched-for class definition existed when the currently
* executing class was compiled, but the definition can no longer be
* found.
*
* @author unascribed
* @since JDK1.0
*/
public
class NoClassDefFoundError extends LinkageError {
private static final long serialVersionUID = 9095859863287012458L;
/**
* Constructs a <code>NoClassDefFoundError</code> with no detail message.
*/
public NoClassDefFoundError() {
super();
}
/**
* Constructs a <code>NoClassDefFoundError</code> with the specified
* detail message.
*
* @param s the detail message.
*/
public NoClassDefFoundError(String s) {
super(s);
}
}
解决方案
以demo中演示的代码为例,只需要在A所在的Project中引入C所在的依赖,即可解决当前问题:
NoClassDefFoundError与ClassNotFoundException
如果JVM或者ClassLoader在加载类时找不到对应的类,就会引发NoClassDefFoundError和ClassNotFoundException。由于不同的ClassLoader会从不同的地方加载类,有时是错误的CLASSPATH引发这类错误,有时是某个库的jar包缺失引发这类错误。NoClassDefFoundError和ClassNotFoundException之间存在一些细微的不同点。
NoClassDefFoundError
- 表示该类在编译阶段还可以找到,但是在运行Java应用的时候找不到了,有时静态块的初始化过程会导致NoClassDefFoundError。
- NoClassDefFoundError是Error,是unchecked,因此也不需要使用try-catch或者finally语句块包围。
-
NoClassDefFoundError 是链接错误,发生在链接阶段,当解析引用的时候找不到对应的类,就会抛出java.lang.NoClassDefFoundError;ClassNotFoundException是异常,发生在运行阶段。
常见Case
如果开发中遇到NoClassDefFoundError,那么最有可能的原因
- jar 包不存在
- 存在多个类加载器和多个目标类,即我们常说的Jar包冲突。比如说上文的B ,如果有两个版本的B存在于依赖中 中, B1是compile scope , B2 是provided scope,如果maven 解析到B1,就不会有NoClassDefFoundError的问题,但是解析到B2,就有NoClassDefFoundError的问题。这种情况迷惑性比较大,第一次build 是可以的,同样的code 第二次build 就不可以了。非常难以定位。解决这种问题,通常需要用mvn dependency tree 来查看到底引用了哪些同名的Artifact,进而exclude 错误的artifacts。
ClassNotFoundException
- 和编译期没什么关系,当你在代码中显式加载类(使用Class.forName())时没有找到对应的类,则会抛出java.lang.ClassNotFoundException。例如加载SQL驱动时,对应的类加载器找不到驱动类。
- ClassNotFoundException是受检异常(checked Exception),因此需要使用try-catch语句块或者try-finally语句块包围,否则会导致编译错误
常见Case
调用Class.forName()、ClassLoader.findSystemClass()和ClassLoader.loadClass()等方法时可能会引起java.lang.ClassNotFoundException。