全面解析JVM加載中初始化的時機

JVM類加載過程

JVM類加載過程分爲幾個階段,分別是加載驗證準備解析初始化加載是把二進制字節碼載入內存,驗證是校驗字節流中包含的信息是否符合當要求,準備是爲靜態變量分配內存並設置靜態變量初始值,解析是把常量池內的符號引用替換爲直接引用,初始化是執行全部靜態變量的賦值動做和靜態語句塊中的語句。更多詳盡分析請閱讀以前的文章《JVM的類加載機制全面解析》,這裏再也不贅述了。java

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。數組

類初始化的時機

對於咱們開發人員,我認爲應該具體瞭解一下初始化階段何時在開始。JVM規範對此作了嚴格規範,有且只有如下5種狀況必須對類進行初始化:微信

  1. 遇到new、getstatic、putstatic或invokestatic這四條字節碼指令時,若是類沒有被初始化過,就須要先進行初始化。對於字節碼指令不瞭解的同窗,可能就是一臉蒙圈了。咱們來講人話,就是:使用new關鍵字實例化對象的時候、讀取和設置一個類的靜態字段(不被final修飾的)和調用一個類的靜態方法的時候。這樣說更容易被理解一些。jvm

  2. 使用java.lang.reflect包中的方法對類進行反射調用的時候,若是類沒有被初始化過,就須要先進行初始化。spa

  3. 當初始化一個類的時候,若是發現它的父類尚未被初始化過,就須要先初始化它的父類。rest

  4. JVM會先初始化要執行的主類,也是包含main()方法的那個類。code

  5. 當使用JDK 1.7的動態語言支持時,若是java.lang.invoke.MethodHandle實例最後的解析結果是REF_getStatic(使用MethodHandle讀取類的靜態字段)、REF_putStatic(使用MethodHandle設置類的靜態字段)、REF_invokeStatic(使用MethodHandle調用類的靜態方法)的方法句柄時,若是這個方法句柄沒有被初始化過,就須要先進行初始化。對象

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。繼承

被動引用

剛剛提到的5種狀況,都會觸發初始化,這些行爲爲稱爲對一個類的主動引用。除了這些之外,全部引用類的方式都不會觸發初始化,被爲被動引用。爲了更好的理解,下面舉幾個被動引用的例子。接口

經過子類引用父類的靜態變量

public class SuperClass {
    static {
        System.out.println("父類正在初始化");
    }

    public static String name = "萬貓學社";
}
public class SubClass extends SuperClass {
    static {
        System.out.println("子類正在初始化");
    }
}
public class OneMoreStudy {
    public static void main(String[] args) {
        System.out.println(SubClass.name);
    }
}

對於靜態變量,只有直接定義這個變量的類纔會被初始化,經過子類引用父類中定義的靜態變量,只會觸發父類的初始化而不會觸發子類的初始化,運行的結果是:

父類正在初始化
萬貓學社

結果中並無「子類正在初始化」。

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

經過數組定義來引用類

public class OneMoreStudy {
    public static void main(String[] args) {
        SuperClass[] arrays = new SuperClass[10];
        System.out.println("數組元素個數:" + arrays.length);
    }
}

這段代碼中使用以前的SuperClass類,定義了一個SuperClass類的一維數組,運行後的結果是:

數組元素個數:10

結果中並無「父類正在初始化」,說明並無觸發SuperClass類的初始化。實際上,有一個名爲「[LSuperClass」的類被初始化了,它是由JVM自動生成的、直接繼承於java.lang.Object,建立動做由字節碼指令newarray觸發。

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

常量

public class ConstClass {
    static {
        System.out.println("有常量的類正在初始化");
    }

    public static final String NAME = "萬貓學社";
}
public class OneMoreStudy {
    public static void main(String[] args) {
        System.out.println(ConstClass.NAME);
    }
}

常量在編譯階段會存入調用類的常量池中,本質沒有直接引用到定義的常量的類,不會觸發定義常量的類的初始化,因此運行的結果是:

萬貓學社

結果中並無「有常量的類正在初始化」。

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

接口初始化的時機

接口也有初始化過程,和類是一致的。不過接口中不能使用「static{}」語句塊,但編譯器仍然會爲接口生成「clinit()」類構造器,用於初始化接口中所定義的成員變量。

接口初始化的時機,基本和以前提到的類的5種狀況基本一致,惟一不同的是第3種狀況:在一個類被初始化時,它的父類也必須被初始化,可是一個接口被初始化時,它的父接口並不要求被初始化。只有在真正使用到父接口時纔會被初始化,好比:引用父接口中定義的常量。

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

結語

此次主要分享了類在何時被初始化,共有5種狀況。除了這種5種狀況的引用叫作被動引用,同時舉了3個被動引用的例子。同時,也提到初始化接口和類有什麼不一樣。

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

相關文章
相關標籤/搜索