虛擬機器類載入機制:類載入時機

mortal同學發表於2018-12-06

虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,就是虛擬機器的類載入機制。

類載入時機

類的生命週期

虛擬機器類載入機制:類載入時機
一個類從載入進記憶體到解除安裝出記憶體,經歷圖示的7個階段:

載入——>驗證——>準備——>解析——>初始化——>使用——>解除安裝。

其中,類的載入包括5個階段:載入——>驗證——>準備——>解析——>初始化。

在類載入的過程中,以下三個過程稱為連線:驗證——>準備——>解析。

因此JVM的類載入過程也可以概括為:載入——>連線——>初始化。

C/C++在執行前需要完成預處理、編譯、彙編、連結;在Java中,類載入(載入、連線、初始化)是在程式執行期間完成的,這種策略雖然會使類載入時稍微增加一些效能開銷,但是會為java應用程式提供高度的靈活性。java可以動態擴充套件語言特性就是依賴執行期間動態載入和動態連結這個特點實現的。比如,如果編寫一個面向介面的程式,可以等到執行時再指定其具體實現類。

載入、驗證、準備、初始化和解除安裝這五個階段的順序確定,而解析階段不一定,在某些情況下可以在初始化階段之後再開始。這是為了支援Java語言的執行時繫結。

類的載入時機

虛擬機器並沒有嚴格約束什麼時候進行類載入,但是對於初始化的時機有嚴格的規定。在初始化之前,載入、驗證、準備都應該開始。

初始化開始的時機:

  1. 遇到new,getstatic,putstatic,invokestatic四個位元組碼指令,如果類沒有進行初始化,則需要先觸發其初始化。
    • 使用new關鍵字例項化物件的時候
    • 讀取或設定一個類的靜態欄位(不包括final以及編譯期放入常量池的靜態欄位)
    • 呼叫一個類的靜態方法
  2. 使用java.lang.reflect包的方法對類進行反射呼叫的時候,如果類沒有進行初始化,則需要先觸發其初始化。
  3. 初始化一個類的時候,若其父類未初始化,先觸發父類初始化,然後初始化本類。
  4. 當虛擬機器啟動時,使用者指定一個需要執行的主類(main()方法的類),虛擬機器會先初始化這個主類。
  5. 使用JDK1.7動態語言支援的時候的一些情況。

有且只有上述五種場景才會觸發類進行初始化,稱為對一個類進行主動引用。除此之外,所有引用類的方式都不會觸發初始化,稱為被動引用。 以下為被動引用的幾個例子

  1. 通過子類引用父類的靜態欄位,不會導致子類初始化
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 InitTest1{
    public static void main(String[] args){
        System.out.println(SubClass.value);
    }
}
複製程式碼

上述程式碼執行會輸出:"SuperClass init!"以及123。對於靜態欄位,只有直接定義這個欄位的類才會被初始化。 2. 通過數字定義引用類,不會觸發此類的初始化

public class InitTest2{
    public static void main(String[] args){
        SuperClass[] sca=new SuperClass[10];
    }
}
複製程式碼

上述程式碼沒有任何輸出。 3. 常量在編譯階段會存入呼叫類的常量池中,本質上並沒有直接引用定義常量的類,因此不會觸發定義常量的類的初始化

class ConstClass{
    static{
        System.out.println("ConstClass init");
    }
    public static final String HELLOWORLD="hello world";
}

public class InitTest3 {
    public static void main(String[] args){
        System.out.println(ConstClass.HELLOWORLD);
    }
}
複製程式碼

上述程式碼只輸出了“hello world”。原因是在編譯階段,已經將常量的值儲存到了InitTest3類的常量池中,在該類中對ConstClass.HELLOWORLD的引用實際上被轉化為InitTest3類自身常量池的引用。

介面的初始化

對於介面的初始化過程,與類的區別主要在於第三種情形,當一個介面在初始化時,並不要求其父介面全部都完成了初始化,只有在真正使用到父介面是,才會初始化。

參考資料

相關文章