天天看點

JVM中類加載的時機

 本文主要記錄下類的加載過程,一個類從加載到記憶體中開始到被解除安裝的整個生命周期作為java程式員來說應該還是要清楚的。

類的生命周期

 類從加載到記憶體中到解除安裝的整個過程中經曆了如下的過程:

JVM中類加載的時機

 這幾個步驟中 驗證,準備,解析這三個步驟有稱為連接配接階段,大體的順序是加載,驗證,準備,解析,初始化,使用和解除安裝,前四個有部分有交叉順序。

類的加載時機

 類加載的時機,也就是類初始化的時機(加載,驗證,準備,解析)。

   遇到 new ,getstatic,putstatic和invokestatic這4條指令的時候,也就是通過new關鍵字執行個體化對象,讀取或者設定一個靜态變量以及調用靜态方法。

   反射調用的時候如果沒有初始化就會加載該類。

   初始化子類的時候發現父類還沒有被初始化就會先初始化父類。

   虛拟機啟動的時候,會初始化主類(含有main方法的類)

   當使用JDK1.7及以上的版本中的動态語言支援時,若一個java.lang.invoke.MethodHandle執行個體最後的解析結果是:REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化,則需要先出發它的初始化過程。

 虛拟機規範中指出有且隻有這5種場景會出發初始化,并且這5種場景的行為稱為對一個類的“主動引用”,除此之外所有引用類的方式都不會觸發初始化,不觸發初始化的也被稱為被動引用。

被動引用的例子

   通過子類引用父類的靜态變量不會初始化子類

class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }

    public static int value = 666;
    public static final String JVM_TEST = "JVM TEST";
}

/**
 * 子類
 */
class SubClass extends SuperClass {

    static {
        System.out.println("SubClass init!");
    }

}

/**
 * 測試
 * @author 波波烤鴨
 * @email [email protected]
 *
 */
public class Test {
    public static void main(String[] args){
        System.out.println(SubClass.value);
    }
}      

輸出結果

SuperClass init!
666      

   通過數組定義來引用類,不會觸發類的初始化

 案例直接用1中的類結構

/**
 * 測試
 * @author 波波烤鴨
 * @email [email protected]
 *
 */
public class Test {
    public static void main(String[] args){
        SuperClass[] supers = new SuperClass[10];
    }
}      

 運作結果并沒有列印出“SuperClass init!”,這說明并沒有對SuperClass進行初始化,定義數組不會觸發類的初始化

   常量在編譯階段會被存入調用類的常量池中,本質上并沒有直接引用到定義常量的類,是以這種場景也不會觸發類的初始化

public class Test {
    public static void main(String[] args){
       // SuperClass[] supers = new SuperClass[12];
       System.out.println(SuperClass.JVM_TEST);
    }
}      

 運作結果也沒有列印出“SuperClass init!”,因為雖然引用了SuperClass的常量,但其實在編譯極端通過常量傳播優化,已經将此常量存儲到了Test類的常量池中,因Test類對此常量的引用,都會轉化為Test類對自身常量池的引用了。這說明SuperClass和Test這兩個類,在編譯階段完成後就沒有任何關系了。

 接口的加載過程和類的加載過程步驟上是一緻的,但是稍有不同的是上面的例子都是用靜态語句塊“static{}”來輸出初始化資訊的,在接口中不能使用“static{}”靜态語句塊。還有一個不同是:當一個類在初始化的時候,要求其父類全部都已經初始化過了,但是一個接口在初始化的時候,不要求其父接口都初始化過,隻有真正使用到父接口的時候(例如:引用父接口中定義的常量)才會初始化。

參考《深入Java虛拟機》