一: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)被加載過(guò)的類,返回的結(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()來(lái)將解密后的字節(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的子類重寫(xiě)findClass(String),而不是loadClass(String name, boolean resolve)這個(gè)方法。
* 除非重寫(xiě),否則此方法在整個(gè)類加載過(guò)程中同步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);
// 沒(méi)有被加載過(guò)
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)來(lái)查找類
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;
}
示例:
過(guò)程:假設(shè)我現(xiàn)在從類路徑下加載一個(gè)類A,
1:那么AppClassLoader會(huì)先查找是否加載過(guò)A,若有,直接返回;
2:若沒(méi)有,去ExtClassLoader檢查是否加載過(guò)A,若有,直接返回;
3:若沒(méi)有,去BootstrapClassLoader檢查是否加載過(guò)A,若有,直接返回;
4:若沒(méi)有,那就BootstrapClassLoader加載,若在E:\Java\jdk1.6\jre\lib*.jar下找到了指定名稱的類,則加載,結(jié)束;
5:若沒(méi)找到,BootstrapClassLoader加載失??;
6:ExtClassLoader開(kāi)始加載,若在E:\Java\jdk1.6\jre\lib\ext*.jar下找到了指定名稱的類,則加載,結(jié)束;
7:若沒(méi)找到,ExtClassLoader加載失敗;
8:AppClassLoader加載,若在類路徑下找到了指定名稱的類,則加載,結(jié)束;
9:若沒(méi)有找到,拋出異常ClassNotFoundException
注意:
1:類的加載過(guò)程只有向上的雙親委托,沒(méi)有向下的查詢和加載,假設(shè)是ExtClassLoader在\Java\jdk1.8\jre\lib\ext*.jar下加載一個(gè)類,那么整個(gè)查詢與加載的過(guò)程與AppClassLoader無(wú)關(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è)類不被加載兩次。
類加載過(guò)程中:檢查時(shí)(調(diào)用findLoadedClass(name)):從下向上檢查是否加載過(guò)指定名稱的類;加載時(shí)(loadClass(name, false)):從上向下加載該類。(在其中任何一個(gè)步驟成功之后,都會(huì)中止類加載過(guò)程)
四:雙親委派機(jī)制的好處
假設(shè)自己編寫(xiě)了一個(gè)java.lang.Object類,編譯后置于類路徑下,此時(shí)在系統(tǒng)中就有兩個(gè)Object類,一個(gè)是rt.jar的,一個(gè)是類路徑下的,在類加載的過(guò)程中,當(dāng)要按照全類名去加載Object類時(shí),根據(jù)雙親委托,BootstrapClassLoader會(huì)加載rt.jar下的Object類,這時(shí)方法結(jié)束,即類路徑下的Object類就沒(méi)有加載了。這樣保證了系統(tǒng)中類不混亂。
五:自定義類加載器
extends ClassLoader,然后重寫(xiě)父類的findClass方法。
注:我們自定義的類加載器沒(méi)有指定父加載器,在JVM規(guī)范中不指定父類加載器的情況下,默認(rèn)采用系統(tǒng)類加載器即AppClassLoader作為其父加載器,所以在使用該自定義類加載器時(shí),需要加載的類不能在類路徑中,否則的話根據(jù)雙親委派模型的原則,待加載的類會(huì)由系統(tǒng)類加載器加載,而不是自定義加載器加載。如果一定想要把自定義加載器需要加載的類放在類路徑中, 就要把自定義類加載器的父加載器設(shè)置為null。
問(wèn):父類有那么多方法,為什么偏偏只重寫(xiě)findClass方法?
因?yàn)镴dk已經(jīng)在loadClass()中幫我們實(shí)現(xiàn)了ClassLoader搜索類的算法,當(dāng)在loadClass方法中搜索不到類時(shí),loadClass方法就會(huì)調(diào)用findClass方法來(lái)搜索類,所以我們只需重寫(xiě)該方法即可。如沒(méi)有特殊的要求,一般不建議重寫(xiě)loadClass搜索類的算法。
問(wèn):什么時(shí)候該使用自定義類加載器呢?
Java中默認(rèn)的三種類加載器都是有默認(rèn)加載路徑的。
當(dāng)需要的加載路徑不是默認(rèn)的三種類加載器的加載路徑時(shí),可以構(gòu)造自定義加載器,指定加載路徑,.class可以是來(lái)自于磁盤(pán)、內(nèi)存、網(wǎng)絡(luò)或者其它。