不可逆的類初始化過程

類的加載過程說複雜很複雜,說簡單也簡單,說複雜是因為細節很多,比如說今天要說的這個,可能很多人都不了解;說簡單,大致都知道類加載有這麼幾個階段,loaded->linked->initialized,為了讓大家能更輕鬆地知道我今天說的這個話題,我不詳細說類加載的整個過程,改天有時間有精力了我將整個類加載的過程和大家好好說說(PS:我對類加載過程慢慢清晰起來得益於當初在支付寶做cloudengine容器開發的時候,當時引入了標準的osgi,解決類加載的問題幾乎是每天的家常便飯,相信大家如果還在使用OSGI,那估計能體會我當時的那種痛,哈哈)。
本文我想說的是最後一個階段,類的初始化,但是也不細說其中的過程,隻圍繞我們今天要說的展開。
我們定義一個類的時候,可能有靜態變量,可能有靜態代碼塊,這些邏輯編譯之後會封裝到一個叫做clinit的方法裏,比如下麵的代碼:
class BadClass{
private static int a=100;
static{
System.out.println("before init");
int b=3/0;
System.out.println("after init");
}
public static void doSomething(){
System.out.println("do somthing");
}
}
編譯之後我們通過javap -verbose BadClass
可以看到如下字節碼:
{
BadClass();
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void doSomething();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String do somthing
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 10: 0
line 11: 8
static {};
flags: ACC_STATIC
Code:
stack=2, locals=1, args_size=0
0: bipush 100
2: putstatic #5 // Field a:I
5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #6 // String before init
10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: iconst_3
14: iconst_0
15: idiv
16: istore_0
17: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
20: ldc #7 // String after init
22: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: return
LineNumberTable:
line 2: 0
line 4: 5
line 5: 13
line 6: 17
line 7: 25
}
我們看到最後那個方法static{}
,其實就是我上麵說的clinit方法,我們看到靜態字段的初始化和靜態代碼庫都封裝在這個方法裏。
假如我們通過如下代碼來測試上麵的類:
public static void main(String args[]){
try{
BadClass.doSomething();
}catch (Throwable e){
e.printStackTrace();
}
BadClass.doSomething();
}
大家覺得輸出會是什麼?是會打印多次before init
嗎?其實不然,輸出結果如下:
before init
java.lang.ExceptionInInitializerError
at ObjectTest.main(ObjectTest.java:7)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.ArithmeticException: / by zero
at BadClass.<clinit>(ObjectTest.java:25)
... 6 more
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class BadClass
at ObjectTest.main(ObjectTest.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
也就是說其實是隻輸出了一次before init
,這是為什麼呢?
clinit方法在我們第一次主動使用這個類的時候會觸發執行,比如我們訪問這個類的靜態方法或者靜態字段就會觸發執行clinit,但是這個過程是不可逆的,也就是說當我們執行一遍之後再也不會執行了,如果在執行這個方法過程中出現了異常沒有被捕獲,那這個類將永遠不可用,雖然我們上麵執行BadClass.doSomething()
的時候catch住了異常,但是當代碼跑到這裏的時候,在jvm裏已經將這個類打上標記了,說這個類初始化失敗了,下次再初始化的時候就會直接返回並拋出類似的異常java.lang.NoClassDefFoundError: Could not initialize class BadClass
,而不去再次執行初始化的邏輯,具體可以看下jvm裏對類的狀態定義:
enum ClassState {
unparsable_by_gc = 0, // object is not yet parsable by gc. Value of _init_state at object allocation.
allocated, // allocated (but not yet linked)
loaded, // loaded and inserted in class hierarchy (but not linked yet)
linked, // successfully linked/verified (but not initialized yet)
being_initialized, // currently running class initializer
fully_initialized, // initialized (successfull final state)
initialization_error // error happened during initialization
};
如果clinit執行失敗了,拋了一個未被捕獲的異常,那將這個類的狀態設置為initialization_error
,並且無法再恢複,因為jvm會認為你這次初始化失敗了,下次肯定也是失敗的,為了防止不斷拋這種異常,所以做了一個緩存處理,不是每次都再去執行clinit,因此大家要特別注意,類的初始化過程可千萬不能出錯,出錯就可能隻能重啟了哦。
最後更新:2017-04-11 19:32:01