Java虛擬機(jī)加載類的全過(guò)程包括,加載,驗(yàn)證,準(zhǔn)備,解析和初始化。

在加載階段,虛擬機(jī)需要完成以下三件事:
- 通過(guò)類的全限名獲取此類的二進(jìn)制字節(jié)流。
- 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)區(qū)
- 在內(nèi)存中生成一個(gè)代表這個(gè)類的Class對(duì)象,作為方法區(qū)的這個(gè)類的各種數(shù)據(jù)訪問(wèn)入口。
可以看出,Java能通過(guò)加載外部的字節(jié)碼來(lái)實(shí)現(xiàn)動(dòng)態(tài)的裝載類,這為Java提供了很大的靈活性。實(shí)現(xiàn)類加載動(dòng)作的代碼叫做類加載器。
比較兩個(gè)類是否相等,只有在兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義,否則盡管兩個(gè)類是同一個(gè)Class文件,只要類加載器不同,那么這兩個(gè)類必定不相等。
雙親委派機(jī)制
絕大部分Java程序都會(huì)用到以下三種系統(tǒng)提供的類加載器:
- BootStrap ClassLoader,負(fù)責(zé)加載<JAVA_HOME>/lib或被-Xbootclasspath指定路徑下的類庫(kù),開(kāi)發(fā)者不可以直接使用
- Extension ClassLoader,負(fù)責(zé)加載<JAVA_HOME>/lib/ext或被java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(kù),開(kāi)發(fā)者可以直接使用
- App ClassLoader,這個(gè)類加載器是ClassLoader.getSystemClassLoader()的返回值,負(fù)責(zé)加載用戶類路徑上所指定的類庫(kù),開(kāi)發(fā)者可以直接使用這個(gè)類加載器,如果應(yīng)用程序沒(méi)有自定義過(guò)類加載器,那么系統(tǒng)默認(rèn)使用這個(gè)類加載器。
應(yīng)用程序一般都會(huì)用到以上三種類加載器,如果有必要我們可以指定自己的類加載器。

圖中展示的這種層次關(guān)系,稱為類加載的雙親委派模型,除了頂層的啟動(dòng)類加載器之外,其余的類加載都要有自己的父類加載器。
類加載器之間的關(guān)系不是以繼承的方式實(shí)現(xiàn),而是以組合的方式實(shí)現(xiàn)。
工作原理
雙親委派的工作流程:如果一個(gè)類收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把請(qǐng)求委派給父類來(lái)實(shí)現(xiàn),每一個(gè)層次的類加載器都是這樣,因此所有的類加載請(qǐng)求都會(huì)最終傳送到啟動(dòng)類加載器,只有當(dāng)父類加載器無(wú)法完成這個(gè)加載請(qǐng)求,子類加載器才會(huì)自己嘗試加載。
要點(diǎn):
- 類加載請(qǐng)求全部交給自己的父類來(lái)操作
- 父類加載器加載不了的自己加載
/**
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先檢查,類是否已經(jīng)被加載
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//只要父類不為空,那么父類來(lái)加載
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//父類加載器無(wú)法加載類,拋出異常
}
//如果父類沒(méi)有加載成功,然后自己尋找對(duì)應(yīng)的類, 我們可以實(shí)現(xiàn)自己的findClass,進(jìn)而實(shí)現(xiàn)自定義類加載器
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
//記錄狀態(tài)
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
雙親委派機(jī)制一個(gè)好處:避免內(nèi)存中出現(xiàn)同樣的字節(jié)碼??梢院芎玫慕鉀Q各個(gè)類加載器的基礎(chǔ)類統(tǒng)一問(wèn)題。
我個(gè)人理解,所有的類最終是由啟動(dòng)類加載器加載的,而啟動(dòng)類加載器我們是不可以直接使用的,啟動(dòng)類加載器的代碼由大神寫(xiě)的,肯定比我們的更安全。常見(jiàn)的系統(tǒng)級(jí)別的類都由啟動(dòng)類加載器加載也保證了安全,不然如果系統(tǒng)級(jí)別的類,由我們寫(xiě)的類加載器加載,這樣多個(gè)系統(tǒng)類出現(xiàn)不一致的情況,讓語(yǔ)言變得很不穩(wěn)定。
破壞雙親委派機(jī)制
雙親委派機(jī)制是一種推薦的使用方式,但不是強(qiáng)制的,雖然絕大部分Java應(yīng)用都是使用雙親委派模型,但是也有例外。比如熱替換,熱部署。
OSGI是Java業(yè)界廣泛認(rèn)可的模塊化標(biāo)準(zhǔn),而OSGI模塊化熱部署的關(guān)鍵是它自定義的類加載器。每一個(gè)模塊都有一個(gè)自己的類加載器,當(dāng)需要更換一個(gè) Bundle(包) 時(shí),Bundle連同類加載器一同替換實(shí)現(xiàn)代碼熱部署。
弄懂了OSGi的精髓,就可以算是掌握了類加載器的精髓
大家一起加油。
最后
本文介紹了類加載中的加載過(guò)程,其中加載過(guò)程由類加載器來(lái)完成,類加載器的加載利用到了雙親委派機(jī)制,通過(guò)代碼,我們可以更好的理解雙親委派機(jī)制,我們也知道了雙親委派機(jī)制不是要強(qiáng)制實(shí)現(xiàn)的,可以試著破壞雙親委派機(jī)制,重新loadClass方法即可..,自定義類加載器需要重寫(xiě)的是findClass方法。
希望能幫到大家
參考
- 《深入理解JVM》