類加載的生命周期:
加載 -> 驗證 -> 準備 -> 解析 -> 初始化 -> 使用 -> 卸載
加載 -> 驗證 -> 準備 -> 初始化 -> 卸載
這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
无拗。 -
clinit
t對于類和接口不是必須的,如果類中沒有靜態(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方法中格侯。