Java虛擬機知識整理——虛擬機類加載的時機

類加載機制是什麼

虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校檢、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。

一些約定

爲了避免語言表達中可能產生的一些理解上的誤會,這裏需要設立兩個說明:

1. 在實際情況中,每個Class文件中都可能有代表着Java語言中的一個類或者接口。後文所說的類可能是指一個類或者一個接口。
2. 這裏所說到的Class文件應當是一串二進制的字節流,無論以任何形式存在都可以。

類加載的生命週期

類聰被加載到虛擬機內存中開始,知道卸載出內存聞之,它的整個生命週期包括:加載、驗證、準備、解析、初始化、使用合卸載7個階段。其中驗證、準備、解析三個部分統稱爲鏈接。

這幾個步驟中,加載、驗證、準備、初始化和卸載這5個階段的順序是確定的,磊的加載過程必須按照這種順序按部就班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之後再開始,這是爲了支持Java語言的運行時綁定(也陳偉動態綁定或晚期綁定。)。這裏說的按部就班的開始也只是按部就班的開始而已。因爲這些階段通常都是互相交叉地混合式金星的,通常會在一個階段的執行過程中調用、激活另外一個階段。

什麼時候開始加載(好吧,實際是什麼時候開始初始化)

什麼情況下需要開始類加載過程中的第一個階段:加載?Java虛擬機規範中沒有金星強制約束,這段可以交給虛擬機的具體實現來自由把我。但是對於初始化階段,虛擬機規範則是嚴格規定了有且只有5種情況下必須立即對類進行“初始化”(而加載、驗證、準備自然需要在此之前開始):

1. 遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時,如果類沒有進行初始化,生成這4條指令的最常見Java代碼場景是:使用new關鍵字實例化對象的時候、讀取或設置一個類的靜態字段(被final修飾、已在編譯器把結果放入常量池的靜態字段除外)的時候,以及一個類的靜態方法的時候。
2. 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要賢觸發其初始化。
3. 當初實話一個類的時候,如果發現其父類黑沒有進行過初始化,則需要賢觸發其父類的初始化。
4. 當虛擬機啓動是,用戶需要制定一個要執行的主類(包含main()方法的那個類),虛擬機慧賢初始化這個主類
5. 當使用JDK1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,並且這個方法句柄對應的類沒有進行過初始化,則需要賢觸發其初始化。
對於這5中慧廚房愛類進行初始化的場景,虛擬機規範中使用了一個很強烈的限定語:“有且只有”,這5中場景中的行爲陳偉對一個類進行主動引用。除此之外,所有引用類的方式鬥不會觸發初始化,稱爲被動引用。

何爲被動引用?

爲了說明什麼是被動引用,這裏舉幾個簡單的代碼例子:
被動使用類字段演示一:
通過子類醫用父類的靜態字段,不會導致子類的初始化

package org.fenixsoft.classloading;
public class SuperClass{
    static{
        System.out.println("SuperClass init!");
    }
    public static int value = 123;
}
public class SubClass extends SuperClass{
    static{
        System.out.println("SubClass init!");
    }
}
//非主動使用類字段演示
public class NotIninialization{
    public static void main(String[] args){
        System.out.println(SubClass.value);
    }
}

這段代碼運行之後,只會輸出“SuoerClass init!”而不會輸出”SubClass init!”。對於靜態字段,只有直接定於這個字段的類纔會被初始化,因此通過其子類類引用父類中定義的靜態字段,只會觸發父類的初始化而不會觸發子類的初始化。至於是否要觸發子類的加載和驗證,在虛擬機規範中並未明確規定,這點取決於虛擬機的具體體現。
被動引用的例子二

package org.fenixsoft.classloading;
puclic class NotInitialization{
    public static void main(String[] args){
        SuperClass[] sca = new SuperClass[10];
    }
}
這段代碼服用了之前代碼的SuperClass,運行之後發現沒有輸出“SuoerClass init!”,說明並沒有觸發類org.fenixsoft.classloading.SuperClass的初始化階段。但是這段代碼裏面觸發了另外一個名爲“Lorg.fenixsoft.classloading.SuperClass”的類的初始化階段,對於用戶代碼來說,這並不是一個合法的類名稱,它是一個虛擬機自動生成的、直接集成於java.lang.Object的子類,創建由字節碼指令newarray觸發。

被動引用的例子三:

package org.fenixsoft.classloading;
public class ConstClass{
    static{
        System.out.ptintln("CpmstClass init!");
    }
    public static final String HELLOWORLD = "hello world";
}
public  class NotInitialization{
    public static void main(String[] args){
        System.out.println(ConstClass.HELLOWORLD);
    }
}

上述代碼運行後,也沒有輸出”CpmstClass init!”,這是因爲雖然Java源碼中引用了ConstClass類中的常量HELLOWORLD,但其實在編譯階段通過常量傳播優化,已經將次常量的值“hello world”存儲到了NotInitialization類的常量池中,以後NotInitialization對常量ConstClass.HELLOWORLD的引用實際鬥被轉化爲NotInitialization類對自身常量池的引用了。也就是說,實際上NotInitialization的Class文件之中並沒有ConstClass類的符號引用入口,這兩個類在編譯成Class之後就不存在任何聯繫了。

接口的加載過程

接口的加載過程和類加載的過程稍有一些不同,針對接口需要做一些特殊說明:接口也有初始化過程,這點與類是一致的,上面的代碼都是用靜態語句塊“static{}”來輸出初始化信息的,而接口中不能使用“static{}”語句塊,但編譯器仍然會爲接口生成“<clint>()”類構造器,用於初始化接口中所定義的類成員變量。接口與類真正有所區別的是前面講述的5種“有且僅有”需要開始初始化的場景的第三種:當一個類在初始化時,要求其父類全部鬥已經初始化過了,但是一個接口在初始化是,並不要求其父接口全部鬥完成了初始化,只有在真正使用到父接口的時候(如引用接口中定義的常量)纔會初始化。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章