JVM中java類的載入時機
Java虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,這就是虛擬機器的載入機制。
類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括了:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和解除安裝(Unloading)七個階段。其中驗證、準備和解析三個部分統稱為連線(Linking),如下如所示。
這七個階段,載入、驗證、準備、初始化和解除安裝這五個階段的順序是確定的,類的載入過程必須按照這個順序來按部就班地開始,而解析階段則不一定,它在某些情況下可以在初始化階段後再開始。
類的生命週期的每一個階段通常都是互相交叉混合式進行的,通常會在一個階段執行的過程中呼叫或啟用另外一個階段。
Java虛擬機器規範沒有強制性約束在什麼時候開始類載入過程,但是對於初始化階段,虛擬機器規範則嚴格規定了有且只有四種情況必需立即對類進行“初始化”(而載入、驗證、準備階段則必需在此之前開始),這四種情況歸類如下:
1.遇到new、getstatic、putstatic或invokestatic這4條位元組碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。生成這4條指令最常見的Java程式碼場景是:使用new關鍵字例項化物件時、讀取或者設定一個類的靜態欄位(被final修飾、已在編譯器把結果放入常量池的靜態欄位除外)時、以及呼叫一個類的靜態方法的時候。
2.使用java.lang.reflect包的方法對類進行反射呼叫的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
3.當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要觸發父類的初始化。
4.當虛擬機器啟動時,使用者需要指定一個執行的主類(包含main()方法的類),虛擬機器會先初始化這個類。
對於這四種觸發類進行初始化的場景,在java虛擬機器規範中限定了“有且只有”這四種場景會觸發。這四種場景的行為稱為對類的主動引用,除此以外的所有引用類的方式都不會觸發類的初始化,稱為被動引用。
下面通過三個例項來說明被動引用:
示例1:
父類SuperClass.java public class SuperClass { static{ System.out.println("SuperClass init!"); } public static int value = 123; } 子類SubClass.java public class SubClass extends SuperClass { static{ System.out.println("SubClass init!"); } } 主類NotInitialization.java public class NotInitialization { public static void main(String[] args) { System.out.println(SubClass.value); } }
輸出結果:
SuperClass init!
123
由結果可以看出只輸出了“SuperClass init!”,沒有輸出“SubClass init!”。這是因為對於靜態欄位,只有直接定義該欄位的類才會被初始化,因此當我們通過子類來引用父類中定義的靜態欄位時,只會觸發父類的初始化,而不會觸發子類的初始化。
示例2:
SuperClass[ ] scs=new SuperClass[11];
如上,當初始化一個物件陣列的時候,也不會觸發類的初始化。
示例3:
public class ConstClass {
static {
system.out.printl("const");
}
public static final int age =123;
}
public class NotInitialization{
public static void main(String[ ] args){
system.out.println(ConstClass.age);
}
此時並不會出現 “const”,因為在NotInitialization類在編譯的時候已經把ConstClass中的變數age放在常量池中了,訪問時直接取出age即可,不會引發ConstClass的初始化。