虛擬機(jī)如何加載Class文件?
Class文件中的信息進(jìn)入到虛擬機(jī)后會(huì)發(fā)生什么變化?
虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制。

何時(shí)加載?
- 遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需要先觸發(fā)初始化。
場(chǎng)景:new 實(shí)例化對(duì)象時(shí)候、讀取或設(shè)置一個(gè)類的靜態(tài)字段的時(shí)候,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。
- 遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需要先觸發(fā)初始化。
- 2)使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)期初始化。
- 3)當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。
- 4)當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。
- 5)當(dāng)使用JDK1.7的動(dòng)態(tài)語言支持的,如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒有進(jìn)行初始化,則需要先觸發(fā)其初始化。
類加載過程
1.加載
在該階段,虛擬機(jī)需要完成以下3件事情:
- 1)通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
- 2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
- 3)在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口。
獲取類的二進(jìn)制字節(jié)流是開發(fā)人員可控性最強(qiáng)的,開發(fā)人員可以定義自己的類加載器去控制字節(jié)流的獲取方式(重寫loadClass()方法)。
數(shù)組類加載 - 1)如果數(shù)組的組件類型是引用類型,則遞歸加載這個(gè)組件類型,數(shù)組將在加載該組件類型的類加載器的類名稱空間上被標(biāo)識(shí)
- 2)如果數(shù)組的組件類型不是引用類型(如int[]數(shù)組),Java虛擬機(jī)將會(huì)把數(shù)組標(biāo)記與引導(dǎo)類加載器關(guān)聯(lián)。
- 3)數(shù)組類的可見性與它的組件類型的可見性一致,如果組件類型不是引用類型,那數(shù)組類的可見性將默認(rèn)為public。
2.驗(yàn)證
驗(yàn)證是連接階段的第一步,這階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。
- 1)文件格式驗(yàn)證
- 2)元數(shù)據(jù)驗(yàn)證
- 3)字節(jié)碼驗(yàn)證
- 4)符號(hào)引用驗(yàn)證
3.準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。
| 數(shù)據(jù)類型 | 零值 |
|---|---|
| int | 0 |
| long | 0L |
| short | (short)0 |
| char | '\u0000' |
| byte | (byte)0 |
| boolean | false |
| float | 0.0f |
| double | 0.0d |
| reference | null |
4.解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程。
- 符號(hào)引用:以一組符號(hào)來描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無歧義地定位到目標(biāo)即可。
- 直接引用:可以是直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。
- 1)類或接口的解析
- 2)字段解析
- 3)類方法解析
- 4)接口方法解析
5初始化
初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。
類加載器
類加載器實(shí)現(xiàn)的是類加載過程中的第一步“通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流”。類加載器在類的層次劃分、OSGi、熱部署、代碼加密等領(lǐng)域大放異彩。
對(duì)于任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身一同確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間。
1.雙親委派模型
- 1)啟動(dòng)類加載器(Bootstrap ClassLoader)一般加載類庫
- 2)擴(kuò)展類加載器(Extension ClassLoader)一般加載擴(kuò)展庫
- 3)應(yīng)用程序類加載器(Application ClassLoader)

父子之間的關(guān)系一般不會(huì)用繼承的關(guān)系來實(shí)現(xiàn),而是都使用組合關(guān)系來復(fù)用父加載器的代碼。
雙親委派模型的工作過程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳遞到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)請(qǐng)求時(shí),子加載器才會(huì)去嘗試自己去加載。
2.破壞雙親委派模型
OSGi中對(duì)類加載器的使用時(shí)很值得學(xué)習(xí)的,弄懂了OSGi的實(shí)現(xiàn),就可以算是掌握了類加載的精髓。