理解ClassNotFoundException與NoClassDefFoundError的區別

上篇文章已經介紹過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錯誤。這兩種異常本質上的側重點還是不一樣的,前者側重在類加載器加載階段找不到類信息,後者則側重在使用階段時卻出現了問題比如實例化依賴類找不到或者類本身就初始化失敗了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章