上篇文章已经介绍过Java的类加载机制,在类加载的过程中我们最常遇到的异常就是:
ClassNotFoundException NoClassDefFoundError
但是你知道他们的区别吗?以及什么情况下发生上面的异常? 如果你还不清楚,那么不着急,我们来仔细分析一下:
先来说说第一个异常提示名字已经非常友好了,就是告诉我们使用类加载器就加载某个类的时候,发现所有的path下面都没有找到,从引导类路径,扩展类路径到当前的classpath下全部没有找到,就会抛出上面的异常,最常见的例子就是加载JDBC驱动包的时候,它的依赖jar并不在classpath里面,如下:
. package class_loader.exception; public class ExceptionTest { public static void main(String[] args)throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); } }
就会抛出异常ClassNotFoundException:
Exception in thread "main" java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at class_loader.exception.ExceptionTest.main(ExceptionTest.java:8) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
这种情况下,其实就是类找不到,通常在执行下面的方法时容易抛出:
Class.forName(), ClassLoader.loadClass() ClassLoader.findSystemClass()
接着我们看NoClassDefFoundError这个异常,严格来说不能叫异常,这种级别属于JVM的ERROR错误了,其严重级别要更高。
这个错误,主要有两种情况:
(1)编译时存在某个类,但是运行时却找不到,如下:
public class A { public void hello(){ System.out.println("A hello"); } } class B { public static void main(String[] args) { A a=new A(); } }
上面的Java类编译后会生成两个类文件,一个A.class,一个B.class,现在我在编译后,删掉了A的class文件,然后直接执行B的main方法,就会抛出 NoClassDefFoundError错误,因为当执行到 A a=new A();这一步的时候,jvm认为这个类肯定在当前的classpath里面的,要不然编译都不会通过,更不用提执行了。既然它存在,那么在jvm里面一定能找到,如果不能找到,那就说明出大事了,因为编译和运行不一致,所以直接抛出这个ERROR,代表问题很严重。
(2)第二种情况,类根本就没有初始化成功,结果你还把它当做正常类使用,所以这事也不小,必须抛出ERROR告诉你不能再使用了。
看下面的一段代码:
public class Loading { static double i=1/0;//故意使得类初始化失败. public static void print(){ System.out.println("123"); } }
调用如下:
public static void main(String[] args) { try { double i=Loading.i; }catch (Throwable e){ //此处,必须用Throwable,用Exception会直接退出. System.out.println(e); } //继续使用. Loading.print(); }
结果如下:
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class class_loader.exception.Loading java.lang.ExceptionInInitializerError at class_loader.exception.NoClassFoundErrorTest.main(NoClassFoundErrorTest.java:18) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
注意这种情况比较特殊,并不是因为编译时和运行时环境不一致导致的,而是对于一个类如果初始化失败后,你还继续使用,那么JVM会认为是不正常的,由于它第一次调用已经失败,JVM就会假设后面继续调用肯定仍然会失败,所以直接抛ERROR给客户端。
这里需要注意,类初始化失败的异常是:
java.lang.ExceptionInInitializerError
也是一个严重级别的错误。
总结:
本文主要对比介绍了ClassNotFoundException与NoClassDefFoundError的区别和发生条件,从上面的测试我们可以分析出,直接采用反射或者类加载器的loadClass方法去动态加载一个所有classpath里面的都不存在的类,类加载器在运行时的load阶段就会直接抛出ClassNotFoundException异常。此外jvm认为这个异常是可以被预知的需要提前被check。对于另一种请情况,如果在编译时候正常,但在运行时执行new关键词的时候,发现依赖类找不到,或者是对于初始化失败的一个类,再次访问其静态成员或者方法,那么会直接抛出NoClassDefFoundError错误。这两种异常本质上的侧重点还是不一样的,前者侧重在类加载器加载阶段找不到类信息,后者则侧重在使用阶段时却出现了问题比如实例化依赖类找不到或者类本身就初始化失败了。