類載入(初始化)時機
首先,區分類載入classloading與類生命週期中的載入loading階段:
類載入classloading通常不單單指類的載入loading階段,而指類從載入loading到初始化initialization階段的這一段時間內的行為。
那麼,什麼情況下需要開始載入loading呢?JVM規範中並沒有強制約束,取決於虛擬機器的具體實現。但是對於初始化initialization階段,JVM規範則嚴格規定了有且只有5種情況必須立即對類進行“初始化”(因此,載入、驗證、準備、解析自然需要在此之前開始)。
因此,面試中常考的“類載入時機”嚴格來說指“類的初始化時機”。用於演示類載入順序的靜態語句塊、靜態變數賦值等操作也是在初始化時機完成的。
類的完整生命週期如下:
什麼時候初始化
4種常見場景:
- 當虛擬機器啟動時,使用者需要指定一個要執行的主類
MainClass
(即包含psvm方法的那個類),虛擬機器會先初始化這個主類。 - 遇到
new
、getstatic
、putstatic
或invokestatic
這4條位元組碼指令時。生成這4條指令的典型Java程式碼場景是:new final
- 當初始化一個類的時候,如果發現其父類還沒有初始化,則需要先觸發其父類的初始化。
- 使用
java.lang.reflect
包的方法對類進行反射呼叫時。
除此之外,比較常用的另一個場景是呼叫 Class.forName()
方法時,可指定 initialize
引數為true,最後通過JNI載入類並初始化。
1種不常見場景:
- 當使用JDK 1.7的動態語言支援時,如果一個
java.lang.invoke.MethodHandle
例項最後的解析結果REF_getStatic
、REF_putStatic
、REF_invokeStatic
的方法控制代碼,並且這個方法控制代碼所對應的類沒有進行過初始化,則需要先觸發其初始化。
對於這5種場景,虛擬機器規範中使用了一個很強烈的限定語:“ 有且只有 ”。
這5種場景中的行為稱為對一個類進行 主動引用
。除此之外,所有引用類的方式都不會觸發初始化,稱為 被動引用
。
我們不需要去關注被動引用。被動引用僅僅指那些看上去向主動引用,實際上卻不是的情況,如:
- 通過子類引用父類的靜態欄位,不會導致子類初始化(如何實現?)
- 通過陣列定義類引用類,不會觸發此類的初始化(JVM為每個陣列生成專有的型別,比如陣列
A[]
的型別是[A
) - 引用常量,不會觸發此類的初始化(常量在編譯階段會存入呼叫類的常量池中(
常量傳播
),執行期也就沒有直接引用到定義常量的類)
參考:
- ofollow,noindex">Java 中對類的主動引用和被動引用