全面解析JVM載入中初始化的時機
JVM類載入過程
JVM類載入過程分為幾個階段,分別是載入、驗證、準備、解析和初始化。載入是把二進位制位元組碼載入記憶體,驗證是校驗位元組流中包含的資訊是否符合當要求,準備是為靜態變數分配記憶體並設定靜態變數初始值,解析是把常量池內的符號引用替換為直接引用,初始化是執行所有靜態變數的賦值動作和靜態語句塊中的語句。更多詳盡分析請閱讀之前的文章《JVM的類載入機制全面解析》,這裡不再贅述了。
歡迎關注微信公眾號:萬貓學社,每週一分享Java技術乾貨。
類初始化的時機
對於我們開發人員,我認為應該具體瞭解一下初始化階段什麼時候在開始。JVM規範對此做了嚴格規範,有且只有以下5種情況必須對類進行初始化:
遇到new、getstatic、putstatic或invokestatic這四條位元組碼指令時,如果類沒有被初始化過,就需要先進行初始化。對於位元組碼指令不瞭解的同學,可能就是一臉蒙圈了。我們來說人話,就是:使用new關鍵字例項化物件的時候、讀取和設定一個類的靜態欄位(不被final修飾的)和呼叫一個類的靜態方法的時候。這樣說更容易被理解一些。
使用java.lang.reflect包中的方法對類進行反射呼叫的時候,如果類沒有被初始化過,就需要先進行初始化。
當初始化一個類的時候,如果發現它的父類還沒有被初始化過,就需要先初始化它的父類。
JVM會先初始化要執行的主類,也是包含main()方法的那個類。
當使用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技術乾貨