[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ā)生順序如下圖:
在上圖,加載、驗(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ā)。