一:ClassLoader
根據(jù)一個(gè)指定的類的名稱莱革,找到或者生成其對(duì)應(yīng)的字節(jié)代碼,然后從這些字節(jié)代碼中定義出一個(gè)Java類讹开,即java.lang.Class類的一個(gè)實(shí)例盅视。
二:ClassLoader的Api
1:loadclass:判斷是否已加載,使用雙親委派模型旦万,請(qǐng)求父加載器闹击,都為空,使用findclass成艘。 拋出的是java.lang.ClassNotFoundException異常赏半。
2:findclass:根據(jù)名稱或位置加載.class字節(jié)碼,然后使用defineClass淆两。(注:加載類的時(shí)候需要全限定類名)
3:findLoadedClass:查找指定名稱的已經(jīng)被加載過的類断箫,返回的結(jié)果是java.lang.Class類的實(shí)例。
4:defineclass: 把字節(jié)數(shù)組中的內(nèi)容轉(zhuǎn)換成Java類琼腔,返回的結(jié)果是java.lang.Class類的實(shí)例(解析定義.class字節(jié)流,返回class對(duì)象)踱葛。拋出的是java.lang.NoClassDefFoundError異常丹莲。
使用場(chǎng)景:對(duì)class文件的加解密操作會(huì)需要使用defineClass()來將解密后的字節(jié)數(shù)組處理成class對(duì)象。
5:resolveClass:鏈接指定的 Java 類尸诽。
三:雙親委派源碼
ClassLoader#loadClass 和 ClassLoader#defineClass
/**
* 使用指定的二進(jìn)制名稱加載類甥材。這個(gè)方法的默認(rèn)實(shí)現(xiàn)是按照以下順序搜索類:
* 調(diào)用findLoadedClass(String name)檢查類是否已經(jīng)加載。
* 在父類加載器上調(diào)用loadClass()方法性含。如果父類null洲赵,則使用虛擬機(jī)內(nèi)置的類加載器(啟動(dòng)類加載器BootStrapClassLoader)。
* 調(diào)用findClass(String)方法查找該類。
* 如果使用上述步驟找到了類叠萍,且解析標(biāo)志為true芝发,則該方法將在結(jié)果類對(duì)象上調(diào)用resolveClass(class)方法。
* 鼓勵(lì)ClassLoader的子類重寫findClass(String)苛谷,而不是loadClass(String name, boolean resolve)這個(gè)方法辅鲸。
* 除非重寫,否則此方法在整個(gè)類加載過程中同步getClassLoadingLock()方法的結(jié)果腹殿。
*
* @param name binary name 二進(jìn)制文件名字独悴,其實(shí)就是類的全限定類名。
* @param resolve true 解析類
* @return 返回的是Class對(duì)象
* @throws ClassNotFoundException 如果類不存在锣尉,則拋出類未發(fā)現(xiàn)異常
*/
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 {
if (parent != null) {
// 如果父加載器不是BootStrapClassLoader刻炒,遞歸調(diào)用loadClass(name, false)
c = parent.loadClass(name, false);
} else {
// BootStrapClassLoader加載器進(jìn)行加載
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 類未發(fā)現(xiàn)時(shí),報(bào)異常
}
if (c == null) {
// 如果父類加載器未找到自沧,再調(diào)用本身(這個(gè)本身包括ext和app)的findClass(name)來查找類
long t1 = System.nanoTime();
c = findClass(name);
// 定義類加載器坟奥,記錄數(shù)據(jù)
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
// 將一個(gè)byte數(shù)組轉(zhuǎn)換為Class類的實(shí)例,會(huì)先去讀取class文件暂幼,然后轉(zhuǎn)成Class類實(shí)例
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError {
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
示例:
過程:假設(shè)我現(xiàn)在從類路徑下加載一個(gè)類A筏勒,
1:那么AppClassLoader會(huì)先查找是否加載過A,若有旺嬉,直接返回管行;
2:若沒有,去ExtClassLoader檢查是否加載過A邪媳,若有捐顷,直接返回;
3:若沒有雨效,去BootstrapClassLoader檢查是否加載過A迅涮,若有,直接返回徽龟;
4:若沒有叮姑,那就BootstrapClassLoader加載,若在E:\Java\jdk1.6\jre\lib*.jar下找到了指定名稱的類据悔,則加載传透,結(jié)束;
5:若沒找到极颓,BootstrapClassLoader加載失斨煅巍;
6:ExtClassLoader開始加載菠隆,若在E:\Java\jdk1.6\jre\lib\ext*.jar下找到了指定名稱的類兵琳,則加載狂秘,結(jié)束;
7:若沒找到躯肌,ExtClassLoader加載失斦叽骸;
8:AppClassLoader加載羡榴,若在類路徑下找到了指定名稱的類碧查,則加載,結(jié)束校仑;
9:若沒有找到忠售,拋出異常ClassNotFoundException
注意:
1:類的加載過程只有向上的雙親委托,沒有向下的查詢和加載迄沫,假設(shè)是ExtClassLoader在\Java\jdk1.8\jre\lib\ext*.jar下加載一個(gè)類稻扬,那么整個(gè)查詢與加載的過程與AppClassLoader無關(guān)。
2:假設(shè)A加載成功了羊瘩,那么該類就會(huì)緩存在當(dāng)前的類加載器實(shí)例對(duì)象C中泰佳,key是(A,C)(其中A是類的全類名尘吗,C是加載A的類加載器對(duì)象實(shí)例)逝她,value是對(duì)應(yīng)的java.lang.Class對(duì)象。
3:上述的加載示例中1睬捶、2黔宛、3都是從相應(yīng)的類加載器實(shí)例對(duì)象的緩存中進(jìn)行查找,進(jìn)行緩存的目的是為了同一個(gè)類不被加載兩次擒贸。
類加載過程中:檢查時(shí)(調(diào)用findLoadedClass(name)):從下向上檢查是否加載過指定名稱的類臀晃;加載時(shí)(loadClass(name, false)):從上向下加載該類。(在其中任何一個(gè)步驟成功之后介劫,都會(huì)中止類加載過程)
四:雙親委派機(jī)制的好處
假設(shè)自己編寫了一個(gè)java.lang.Object類徽惋,編譯后置于類路徑下,此時(shí)在系統(tǒng)中就有兩個(gè)Object類座韵,一個(gè)是rt.jar的险绘,一個(gè)是類路徑下的,在類加載的過程中誉碴,當(dāng)要按照全類名去加載Object類時(shí)宦棺,根據(jù)雙親委托,BootstrapClassLoader會(huì)加載rt.jar下的Object類翔烁,這時(shí)方法結(jié)束渺氧,即類路徑下的Object類就沒有加載了旨涝。這樣保證了系統(tǒng)中類不混亂蹬屹。
五:自定義類加載器
extends ClassLoader侣背,然后重寫父類的findClass方法。
注:我們自定義的類加載器沒有指定父加載器慨默,在JVM規(guī)范中不指定父類加載器的情況下贩耐,默認(rèn)采用系統(tǒng)類加載器即AppClassLoader作為其父加載器,所以在使用該自定義類加載器時(shí)厦取,需要加載的類不能在類路徑中潮太,否則的話根據(jù)雙親委派模型的原則,待加載的類會(huì)由系統(tǒng)類加載器加載虾攻,而不是自定義加載器加載铡买。如果一定想要把自定義加載器需要加載的類放在類路徑中, 就要把自定義類加載器的父加載器設(shè)置為null。
問:父類有那么多方法霎箍,為什么偏偏只重寫findClass方法奇钞?
因?yàn)镴dk已經(jīng)在loadClass()中幫我們實(shí)現(xiàn)了ClassLoader搜索類的算法,當(dāng)在loadClass方法中搜索不到類時(shí)漂坏,loadClass方法就會(huì)調(diào)用findClass方法來搜索類景埃,所以我們只需重寫該方法即可。如沒有特殊的要求顶别,一般不建議重寫loadClass搜索類的算法谷徙。
問:什么時(shí)候該使用自定義類加載器呢?
Java中默認(rèn)的三種類加載器都是有默認(rèn)加載路徑的驯绎。
當(dāng)需要的加載路徑不是默認(rèn)的三種類加載器的加載路徑時(shí)完慧,可以構(gòu)造自定義加載器,指定加載路徑条篷,.class可以是來自于磁盤骗随、內(nèi)存、網(wǎng)絡(luò)或者其它赴叹。