虛擬機(jī)類加載機(jī)制

[toc]

類加載過(guò)程概覽

類從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到卸載出內(nèi)存為止,他的整個(gè)生命周期包括以下7個(gè)階段:

  • 加載(Loading)
  • 驗(yàn)證(Verification)
  • 準(zhǔn)備(Preparation)
  • 解析(Resolution)
  • 初始化(Initialization)
  • 使用(Using)
  • 卸載(Unloading)

其中前五個(gè)階段為類的加載全過(guò)程,在后面會(huì)進(jìn)行詳細(xì)介紹。而驗(yàn)證、準(zhǔn)備、解析3個(gè)部分統(tǒng)稱為連接(Linking)。這7個(gè)階段的發(fā)生順序如下圖:

image

在上圖,加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這5五個(gè)階段的順序是確定的,類的加載過(guò)程必須按照這中順序按部就班的開(kāi)始(開(kāi)始而不是完成,這些階段是互相交叉著進(jìn)行的,在一個(gè)階段執(zhí)行過(guò)程中就會(huì)激活另一個(gè)階段),而解析階段則不一定:他在某些情況下可以在初始化階段之后再開(kāi)始,這是為了支持Java的運(yùn)行時(shí)綁定(也稱為動(dòng)態(tài)綁定或晚期綁定)。

類初始化的時(shí)機(jī)

對(duì)于類加載過(guò)程的第一個(gè)階段:加載,JVM規(guī)范沒(méi)有進(jìn)行強(qiáng)制約束其開(kāi)始時(shí)機(jī),可交由JVM的具體實(shí)現(xiàn)來(lái)自由把握。但是對(duì)于才初始化階段,JVM規(guī)范嚴(yán)格規(guī)定了只有下列五種情況必須對(duì)類進(jìn)行初始化(很自然的,加載、驗(yàn)證、準(zhǔn)備需要在之前開(kāi)始)。

  • 遇到new、getstatic、putstatic、invokestatic這四條字節(jié)碼指令時(shí),如果類沒(méi)用進(jìn)行初始化,則必須先觸發(fā)其初始化。最常見(jiàn)的生成這4條指令的場(chǎng)景:使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候;讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候;以及調(diào)用一個(gè)類的靜態(tài)方法時(shí)候。
  • 使用java.lang.reflect包的方法是進(jìn)行反射調(diào)用的時(shí)候,如果類沒(méi)有初始化,則必須要先觸發(fā)初始化。
  • 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行初始化,則需要先觸發(fā)父類的初始化。
  • 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要制定一個(gè)要執(zhí)行的主類(包含main方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類
  • 當(dāng)前使用JDK1.7的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒(méi)用進(jìn)行初始化,則需要先觸發(fā)初始化;

以上五種場(chǎng)景中的行為稱為對(duì)一個(gè)類進(jìn)行主動(dòng)引用。除此之外,所有引用類的方式都不會(huì)觸發(fā)初始化,被稱為被動(dòng)引用。被動(dòng)應(yīng)用哪個(gè)常見(jiàn)的例子包括:

  • 通過(guò)子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化。
  • 通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始話
  • 常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒(méi)用直接引用定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化。

接口的加載過(guò)程和類的加載過(guò)程略有不同,他們真正的確保在于前文提到的5中需要開(kāi)始初始化場(chǎng)景中第三種:當(dāng)一個(gè)類在初始化時(shí),要求其父類全部都已初始化過(guò)了,但是一個(gè)接口在初始化時(shí),并不要求其父接口全部完成了初始化,只有在真正使用到父接口的時(shí)候(如引用接口中定義的常量)才會(huì)初始化。

類加載過(guò)程詳解

加載

加載類加載(Class Loading)過(guò)程的一個(gè)階段,兩者不要混淆。虛擬機(jī)規(guī)范中規(guī)定了在類在加載階段,JVM需要完成以下三件事:

  • 通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流。
  • 將這個(gè)字節(jié)流所代表的的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)存儲(chǔ)結(jié)構(gòu)
  • 在內(nèi)存中生成一個(gè)代表這個(gè)類java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口。

這三點(diǎn)要求不算具體,在JVM實(shí)現(xiàn)是靈活度很大。例如上面第一條,他沒(méi)用知名二進(jìn)制字節(jié)流要從一個(gè)Class文件中獲取,準(zhǔn)確的說(shuō)沒(méi)用指明要從哪里獲取、怎樣獲取,這也為許多Java技術(shù)提供了基礎(chǔ),例如

  • 從ZIP包讀取,這是很常見(jiàn)的,最終成為日后的JAR、EAR、WAR的格式基礎(chǔ)。
  • 從網(wǎng)絡(luò)中獲取,這種場(chǎng)景最典型的應(yīng)用就是Applet
  • 運(yùn)行時(shí)計(jì)算生成,這種場(chǎng)景使用最多的就是動(dòng)態(tài)代理,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass的代理類的二進(jìn)制字節(jié)流。
  • 由其他文件生成,典型的場(chǎng)景就是JSP應(yīng)用,即由JSP文件生成對(duì)應(yīng)的Class類。
  • 從數(shù)據(jù)庫(kù)讀取,這種場(chǎng)景相對(duì)少見(jiàn),例如有些中間件服務(wù)器,可以把程序安裝到數(shù)據(jù)庫(kù)中來(lái)完成程序代碼在集群的分發(fā)。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容