深入理解Java虛擬機05--虛擬機類加載機制
一.前言
我們一定心里有個疑問,我們那個多態(tài)是怎么回事?我們指定的一個接口,卻可以等到運行時可以對應(yīng)于不同的實現(xiàn)類。這是因為,Java有個特性就是依賴運行期動態(tài)加載和動態(tài)連接,這樣實現(xiàn)了Java可以動態(tài)進行擴展。我們甚至可以從網(wǎng)絡(luò)或者其他的地方加載一個二進制流作為程序的一部分。所以,我們通過編譯器將我們寫的Java文件代碼編譯成Class文件,程序跑起來的時候通過加載器。
二.加載過程

1、 加載(loading)
三件大事
- 1、通過類的全限定名來定義這個類的二進制流
- 2、將字節(jié)流代表的靜態(tài)存儲結(jié)構(gòu)變成方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
- 3、在方法區(qū)生成一個java.lang.Class對象,作為方法區(qū)數(shù)據(jù)的訪問入口
自定義類加載器:重寫loadClass()方法
- 一個區(qū)別:數(shù)組類本身不通過類加載器創(chuàng)建,而是由虛擬機直接創(chuàng)建,但是數(shù)組的元素還是需要類加載器創(chuàng)建的;
2、 連接
(1)、 驗證
- 確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機的要求,避免導(dǎo)致系統(tǒng)奔潰
- 驗證類型
- 文件格式驗證:保證輸入的字節(jié)流能夠正確的解析并存儲于方法區(qū)之內(nèi),格式上符合Java類型信息;
- 元數(shù)據(jù)驗證:對元數(shù)據(jù)信息進行語義上的校驗
- 字節(jié)碼驗證:通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的,保證方法運行時不會危害虛擬機;
- 符號引用驗證:驗證類是否找到到(NoSuchMothodError),訪問性是否正常等等,保證解析動作能正常運行;
- 驗證是重要但不是必須的,對于反復(fù)驗證呢過的Class可以考慮使用-Xverify:none參數(shù)來關(guān)閉大部分的類驗證措施;
(2)、準(zhǔn)備:為類變量分配內(nèi)存并設(shè)置初始值(如int為0)。
(3)、解析:
- 可以放在初始化之后進行,比如需要動態(tài)加載的情況下
- 將常量池中的符號引用替換為直接引用的過程
- 符合引用:一組符號來描述所引用目標(biāo),與虛擬機內(nèi)存布局無關(guān);
- 直接引用:直接指向目標(biāo)的指針、相對偏移量或者是一個能間接定位到目標(biāo)的句柄。和內(nèi)存布局有關(guān),目標(biāo)已經(jīng)在內(nèi)存中;
- invokedynamic:動態(tài)調(diào)用點限定符,程序?qū)嶋H運行到這條指令的時候,解析動作才能進行;
- 解析種類
- 類或接口的解析
- 字段解析
- 類方法解析
- 接口方法解析
3、初始化
- 必須要立即進行初始化的情況如下(主動引用:主動觸發(fā)引用的類進行初始化):
- new(實例化對象)、getstatic、putstatic(讀取或者設(shè)置靜態(tài)字段)、invokestatic(調(diào)用類靜態(tài)方法)這4個字節(jié)碼指令
- java.lang.reflect包的方法反射調(diào)用,類若沒有初始化必須先進行初始化
- 初始化一個類,父類沒有初始化,必須先初始化父類(但是接口不用,只有用到父接口時,才會初始化)
- 虛擬機指定了main方法的類
- JDK1.7,java.lang.invoke.MethodHandle解析的句柄為REF_ getStatic、REF_ putStatic、REF_ invokeStatic,對應(yīng)的類還沒有被初始化
- 被動引用
- 子類引用父類的靜態(tài)字段,只初始化父類,而不會觸發(fā)初始化子類
- 通過數(shù)組定義來引用類,不會觸發(fā)此類的初始化
- 常量在編譯階段會存入到調(diào)用類的常量池中,本質(zhì)上沒有直接應(yīng)用調(diào)用到定義了常量的類,因此定義了常量的這個類不會觸發(fā)初始化
- < cinit >()方法:編譯器自動收集類中的類變量的賦值動作和static代碼塊中的程序語句,并且能自動觸發(fā)父類的< cinit >()方法先進行初始化。遇到多線程并發(fā)的時候,會自動加鎖,其他的線程會被阻塞。直到執(zhí)行完畢。
4、 使用
5、 卸載
三.類加載器
1、唯一性
- 對于任何一個類,都需要和這個類的加載器與這個類共同確定在Java虛擬機中的唯一性,這里說的唯一性指的是“相等”,也就是我們平時說的Class對象的equals()、isAssiganableFrom()、isInstance()方法的返回結(jié)果;
2、雙親委派模型
- 特點:
當(dāng)一個類的加載器收到了加載請求,不會自己先動手,而是委派給這個類的父類進行加載,如果找不到加載不了就反饋回來自己加載。這樣的話,讓Java的類一出生就有了很好的層次父子關(guān)系。當(dāng)然也有一些手段去破壞這種關(guān)系而獲得某種效果。
- 破壞:
雙親委派模型可以被破壞,推薦重寫findClass()方法,而不是loadClass(),應(yīng)用于熱部署等技術(shù);
四.小結(jié)
我們通過編譯器先將我們寫的.java代碼編譯為可執(zhí)行的.class文件,那么如果我們需要真正的執(zhí)行這個代碼,還需要一個過程。這個時候加載器的角色就來了,加載器將首先要加載可執(zhí)行文件,并變換數(shù)據(jù)結(jié)構(gòu)。在初始化之前,我們還需要進行驗證和準(zhǔn)備。解析的過程可以在初始化之前,也可以在初始化之后(實現(xiàn)動態(tài)加載的時候—)。觸發(fā)初始化的條件有幾種,分為被動引用和主動引用兩大類。我們可以理解為主動引用是我們主動的觸發(fā)了本Class的初始化,比如New 這個對象的實例。 但是,也存在我們在本類中引用到了其他的類,比如說父類,其他類的常量。如果,我們的操作不是上述的主動引用,其結(jié)果是沒有觸發(fā)本Class的初始化,而是間接的觸發(fā)了別的Class進行初始化工作。我們稱這個為被動引用。對于任何一個類,我們通過類和這個類的加載器共同確定在JVM中的唯一性,為了保證父類和子類的層次關(guān)系。我們在有需求觸發(fā)子類的初始化時,必須先完成父類的初始化工作,一直向上追溯,從上到下依次完成初始化。這就是所謂的雙親委派模型。雙親委派模型也是可以被破壞的,在熱部署技術(shù)中有應(yīng)用。
浙公網(wǎng)安備 33010602011771號