JVM 對 NoClassDefFoundError 的「緩存」

問題

今天在排查一個線上的問題,線上的一個應用在初始化一個類的靜態字段的時候出現了 NoClassDefFoundError,而且在致使 NoClassDefFoundError 出現的根本緣由消失後,後續再次嘗試初始化這個類的時候,持續出現了 NoClassDefFoundErrorjava

因而懷疑 JVM 是否是對一個類的 NoClassDefFoundError 作了緩存,在第一次加載這個類出現 NoClassDefFoundError 之後,後續再嘗試加載就直接拋出 NoClassDefFoundError緩存

實驗

爲了證明本身的猜測,嘗試設計了一個簡單的實驗,一個涉及三個類oop

public class Test1 {
    static Test2 test2 = new Test2();
}
public class Test2 {
}
public class Test {
    public static void main(String... args) throws Exception {
        while(true) {
            System.out.println("================================");
            try {
                new Test1(); // 嘗試實例化 Test1,觸發 NoClassDefFoundError
            } catch (Throwable e) {
                e.printStackTrace();

                try {
                    Test.class.getClassLoader().loadClass("Test2"); // 嘗試加載 Test2,用於證明當將 
                                                                    // Test2.class 拷貝到 ClassPath 下的時候,
                                                                    // Test2 就能夠加載到了。
                } catch (Throwable ex) {
                    ex.printStackTrace();
                }
            }
            Thread.sleep(3000);
        }
    }
}

上述類的的做用是:Test2 是一個空的類,Test1 裏面有一個 Test2 的靜態成員。Test 是程序的主入口,在一個無限循環內部,不斷地嘗試去實例化 Test1,而且在加載 Test1 出現異常的時候,嘗試加載一下 Test2。this

實驗的步驟是:spa

  1. 編譯以上類,運行 javac Test.java
  2. 將生成出的 Test2.class 重命名成 Test2.class.bak
  3. 運行 java Test,這個時候程序去加載 Test1 的時候,就會出現 NoClassDefFoundError,而且在嘗試加載 Test2 的時候,會出現 ClassNotFoundException
  4. 將第二步重命名的 Test2.class.bak 該回成 Test2.class,這個時候程序去加載 Test1 的時候,就會出現 NoClassDefFoundError,在加載 Test2 的時候,不會出現 ClassNotFoundException

實驗的第二步的目的是爲了程序在加載 Test1 的時候由於找不到 Test2 出現 NoClassDefFoundError,第四步是爲了和第二步作對照,說明在後續程序能夠加載到 Test2 的時候,在實例化 Test1 的時候,依舊出現 NoClassDefFoundError設計

在個人機器上,按照上面的方式去操做,結果以下:code

NoClassDefFoundError

結果正如預期,即便在後面 Test2 在 ClassPath 下的時候,NoClassDefFoundError 依舊出現,因此 JVM 裏面確定有地方對 NoClassDefFoundError 作了緩存。get

JVM 裏面的實現

帶着這個疑問,請教了部門裏面的 JVM 專家,這個猜想獲得了證明,而且他給出了 JVM 內部具體處理這段邏輯的代碼,處理的代碼在 JDK 的 instanceKlass.cpp 這個文件裏面:it

bool instanceKlass::link_class_impl(
     instanceKlassHandle this_oop, bool throw_verifyerror, TRAPS) {
   // check for error state
   if (this_oop->is_in_error_state()) {
     ResourceMark rm(THREAD);
     THROW_MSG_(vmSymbols::java_lang_NoClassDefFoundError(),
                this_oop->external_name(), false);
   }
   // return if already verified
   if (this_oop->is_linked()) {
     return true;
   }

而且在 instanceClass.hpp 這個文件中,定義了類的 _init_state,其中,is_in_error_state 這個方法的定義以下:io

bool is_in_error_state() const           { return _init_state == initialization_error; }
相關文章
相關標籤/搜索