ClassNotFoundException和NoClassDefFoundError
最近工作中发现老是有同事遇到NoClassDefFoundError以及ClassNotFoundException这两种异常,很对人对此比较困惑,于是研究了一下并整理了此文档,希望对大家有所帮助。
ClassNotFoundException这个比较好理解,就是找不到类。其直接原因是:当应用调用类的forName方法、调用ClassLoader的findSystemClass方法、调用ClassLoader的loadClass方法时找不到指定的类。
NoClassDefFoundError这个比较容易让人疑惑些,其直接原因是:当Java虚拟机或者ClassLoader实例试图加载类时,类却找不到了,但是在编译期是没有问题的,只是在运行期找不到。
那么该如何理解它们?
ClassNotFoundException
对于ClassNotFoundException理解起来比较简单直接,就是在运行时调用诸如Class.forName等方法,将类的全限定名称作为参数,但是在运行时找不到这个名称的类。
比如,当我们没有依赖JDBC包的情况下试图加载JDBC驱动,就会抛出这个异常:
@Test(expected = ClassNotFoundException.class) public void givenNoDrivers_whenLoadDriverClass_thenClassNotFoundException() throws ClassNotFoundException { Class.forName("oracle.jdbc.driver.OracleDriver"); }
当然还有一些其它情况,最常见的是没有依赖相关jar包,其他如类名写错了,类名不合法,类没有放到classpath上等等。还有一点就是ClassNotFoundException是一个可检查异常,它直接继承自Exception类。
下面这段是JDK源码注释,大家可以参考一下:
public class ClassNotFoundException extends ReflectiveOperationException (extends Error) 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. but no definition for the class with the specified name could be found.
NoClassDefFoundError
NoClassDefFoundError不是一个Exception而是一个致命Error。当Java虚拟机试图做如下操作的时候找不到类的定义:
通过new 关键字去实例化一个类
通过方法调用去加载一个类
进一步来说,这个错误出现在编译器可以成功编译类,但是在运行期无法定位到这个类文件。至于什么原因导致无法定位这个类文件,存在很多情况。一种常见的场景就是执行一段静态代码块或者初始化一个静态变量时抛出异常,导致类无法正常初始化。因为代码是静态的,所以编译是没有问题的,只是在运行时才会抛出异常,导致类无法实例化,最终导致NoClassDefFoundError。
下面我们通过一个例子来理解:
先定义一个初始化会失败的类:
public class ClassWithInitErrors { static int data = 1 / 0;//静态变量data初始化会失败,因为0除不尽。注意这个变量是静态的,所以编译是通过的。 }
再定义一个类去实例化ClassWithInitErrors:
public class NoClassDefFoundErrorExample { public ClassWithInitErrors getClassWithInitErrors() { ClassWithInitErrors test; try { test = new ClassWithInitErrors(); } catch (Throwable t) { System.out.println(t);//抛出的异常是ExceptionInInitializerError } test = new ClassWithInitErrors();//因为ClassWithInitErrors初始化会抛异常,导致无法实例化ClassWithInitErrors,异常栈会显示此处有问题 return test; } public static void main(String[] args) { NoClassDefFoundErrorExample sample= new NoClassDefFoundErrorExample(); sample.getClassWithInitErrors(); } }
打印结果如下:
java.lang.ExceptionInInitializerError Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class com.tf56.disconfdemo.util.ClassWithInitErrors at com.tf56.disconfdemo.util.NoClassDefFoundErrorExample.getClassWithInitErrors(NoClassDefFoundErrorExample.java:16) at com.tf56.disconfdemo.util.NoClassDefFoundErrorExample.main(NoClassDefFoundErrorExample.java:23)
下面这段是JDK源码注释,大家可以参考一下:
public class NoClassDefFoundError extends LinkageError (extends Error) 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.
解决方案
这两个异常都涉及到在运行期间无法加载某个类,排查起来可能有些困难。但也有一些常见的解决方法,具体如下:
首先确定出现异常的类或者jar是否在classpath里面,如果没有,要添加进去。最常见的情况就是pom里面缺少依赖的jar包
依赖包发生了冲突,比如应该依赖高版本jar包,但又其它包传递依赖了低版本jar包,导致高版本中某些类找不到
如果发现类在classpath里面,很有可能是classpath被重写了,需要再次确定应用准确的classpath
如上面的例子,检查日志中是否含有ExceptionInInitializerError异常,静态成员初始化失败是也会导致
如果应用中有多个类加载器也可能会出现这种情况,因为一个类加载器加载的类有可能无法在另一个类加载器中使用
最后总结
ClassNotFoundException与NoClassDefException核心区别是,前者强调运行时无法匹配到指定参数名称的类,后者强调编译时没问题,运行时却无法实例化一个类。
最常见的解决方法是检查是否依赖了相关包或者相关包是否有冲突。