1. 程式人生 > >JVM規範中初始化類的5種情況(有且僅有)

JVM規範中初始化類的5種情況(有且僅有)

類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入、驗證、準備、解析、初始化、使用和解除安裝 7個階段。其中驗證、準備、解析3個部分統稱為連線。

載入、驗證、準備、初始化和解除安裝這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_putStatic、REF_getStatic、REF_invokeStatic的方法控制代碼,並且此方法控制代碼所對應的類沒有進行初始化時,需要初始化該類。

除以上5種情況外,所有引用類的方式都不會觸發初始化,稱為被動引用。三種被動引用的例子如下:

package NormalTest;

/**
 * Created by ZhangAnmy on 18/9/30.
 *
 * 結果:
 SuperClass init!
 123
 * 通過子類引用父類的靜態欄位,只會觸發父類的初始化而不會觸發子類的初始化.
 * 對於Sun HotSpot虛擬機器,可通過 -XX:+TraceClassLoading引數觀察到此操作會導致子類的載入
 */

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

    public static int value = 123; //此處沒有final修飾,近static修飾符
}

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

public class NotInitialization0930 {
    public static void main(String[] args)
    {
        System.out.println(SubClass.value);
    }
}
/*
* 沒有輸出結果
* 通過陣列定義來引用類,不會觸發此類的初始化
*/
public class NotInitialization0930 {
    public static void main(String[] args)
    {
//        System.out.println(SubClass.value);
        SuperClass[] sca = new SuperClass[10];
    }
}
/*
* 輸出結果:JD (沒有輸出 ConstClass init!)
* 常量在編譯階段會存入呼叫類的常量池中,本質上並沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化.
*/
class ConstClass {
    static {
        System.out.println("ConstClass init!");
    }
    public static final String jd = "JD"; //此處有final修飾符
}

public class NotInitialization0930 {
    public static void main(String[] args)
    {
//        System.out.println(SubClass.value);
//        SuperClass[] sca = new SuperClass[10];
        System.out.println(ConstClass.jd);
    }
}

解析: 在Java原始碼中引用了ConstClass類中的常量jd,但其實在編譯階段通過常量傳播優化,已經將此常量的值JD儲存到了NotInitialization0930類的常量池中,以後NotInitialization0930對常量ConstClass.jd的引用實際都轉化為NotInitialization0930類對自身常量池的引用了,即:NotInitialization0930的Class檔案之中並沒有ConstClass類的符號引用入口,這兩個類在編譯成Class之後就不存在任何聯絡了。