《深入理解jvm》讀書筆記之——類加載機制(類的初始化)

類加載的生命周期:

加載 -> 驗證 -> 準備 -> 解析 -> 初始化 -> 使用 -> 卸載

加載 -> 驗證 -> 準備 -> 初始化 -> 卸載 這5個階段順序是確定的,klass的加載過程一定會按照這個順序執(zhí)行。為了支持java的運行時綁定,解析階段在某些情況下會在初始化之后才進行。

類的初始化階段

對于加載這個階段是跟具體的虛擬機實現(xiàn)有關(guān),對于整個類加載階段最重要的就是初始化這個階段.

JVM執(zhí)行初始化的情況

對于Hotspot虛擬機而言,遇見以下這5種情況就需要進行初始化:

  • 遇到new、getstatic、putstatic、invokestatic,如果類還沒進行初始化的時候就進行初始化。生成這4種指令最常見的就是:new一個實例化對象、讀取或設(shè)置一個類的靜態(tài)字段(被final修飾、已在編譯時吧結(jié)果放在常量池的靜態(tài)字段排除),已經(jīng)調(diào)用一個類的靜態(tài)方法時。
  • 使用java的反射對類進行反射調(diào)用。
  • 初始化類之間,檢測父類是否初始化,否則先初始化父類。
  • 虛擬機啟動時,用戶需要制定一個要執(zhí)行的主類(包含main方法的類),虛擬機會先初始化這個類
  • 使用jdk7以上動態(tài)語言支持時,如果一個methodHandle實例最后的解析結(jié)果REF_getstatic、REF_putstatic、REF_invokestatic的方法句柄,并且這個方法的句柄對應(yīng)的類沒有初始化的時候。

這里我們需要注意的點,上面的五種情況指的是主動的引用方式,除了上面5種主動引用之外的被動引用是不會觸發(fā)初始化的.

類的被動引用實例:

情況一:通過子類來引用父類的靜態(tài)字段,是只會執(zhí)行父類的初始化而子類不會初始化的,但是Hotspot虛擬機下會觸發(fā)子類的加載和驗證。

情況二:聲明一個數(shù)組類型的類。因為jvm會調(diào)用newarray生成一個繼承自object的子類,這個類代表了對應(yīng)的這個類型的數(shù)組類型。

情況三:A引用了B中fianl修飾過的靜態(tài)屬性不會導(dǎo)致B的初始化,因為經(jīng)過編譯器的優(yōu)化,A中引用的這個B的屬性元素已經(jīng)在編譯時期存儲到了A類下的常量池中,所以其實A下的引用來自于對自身常量池的引用。

我們這里還需要注意的一點是接口和類不同的就是接口的父接口只有在真正被使用的時候才會被初始化。

類的初始化之clinit方法

對于jvm而言,類的初始化也就是執(zhí)行clinit方法,那么什么是clinit方法?

clinit方法是有編譯器自動收集類中的所有變量的賦值動作和靜態(tài)語句塊中的語句合并產(chǎn)生的一個用于jvm執(zhí)行類的初始化的方法。

需要注意以下幾點:

  • clinit方法不需要顯示的調(diào)用父類構(gòu)造器,虛擬機會保證子類的clinit方法執(zhí)行之前父類的clinit方法已經(jīng)調(diào)用完畢,因此虛擬機中第一個被執(zhí)行clinit方法的肯定是Object
  • clinitt對于類和接口不是必須的,如果類中沒有靜態(tài)塊,也沒有對變量的賦值操作,編譯器可以不為這個類生產(chǎn)clinit方法。
  • 執(zhí)行接口的clinit方法不需要先執(zhí)行父接口的clinit方法,只有當(dāng)父接口中定義的變量被使用,父接口才會初始化。另外接口的實現(xiàn)類在初始化也一樣不會執(zhí)行接口的clinit方法。
  • jvm會保證一個類的clinit方法在多線程環(huán)境下被正確加鎖同步,也就是說類的初始化是線程安全的,同時需要注意的是,如果一個線程執(zhí)行clinit方法時有很耗時的操作,就會阻塞其他也要初始化的這個類的線程。

驗證猜想的小技巧

關(guān)于我們文章上述初始化過程中,如何驗證,我們可以吧代碼在寫在類的static塊里,就能驗證我買的猜想了。原理就在上文關(guān)于clinit方法中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容