ClassNotFoundException和NoClassDefFoundError

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核心區別是,前者強調運行時無法匹配到指定參數名稱的類,後者強調編譯時沒問題,運行時卻無法實例化一個類

最常見的解決方法是檢查是否依賴了相關包或者相關包是否有衝突

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