ClassLoader源碼解析

一: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ò)或者其它。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ClassLoader 即類加載器,其作用是在JVM虛擬機(jī)中動(dòng)態(tài)的加載class文件。眾所周知,我們寫(xiě)的Java應(yīng)...
    sunnyaxin閱讀 1,027評(píng)論 0 1
  • 提起熱修復(fù)以及插件化,相信大家肯定不陌生,而無(wú)論是熱修復(fù)還是插件化,其理論依據(jù)就是Android 類加載機(jī)制。 類...
    奔跑吧哈哈閱讀 1,010評(píng)論 0 10
  • 1.JVM運(yùn)行流程 JVM運(yùn)行流程如下圖所示: 2.JVM基本結(jié)構(gòu) JVM基本機(jī)構(gòu)包括:類加載器,執(zhí)行引擎,運(yùn)行時(shí)...
    landy8530閱讀 1,688評(píng)論 0 5
  • 1.簡(jiǎn)介 類加載器負(fù)責(zé)加載類。給定類的二進(jìn)制名,類加載器會(huì)嘗試定位或生成構(gòu)成類定義的數(shù)據(jù)。典型的策略是將名稱轉(zhuǎn)換為...
    王偵閱讀 636評(píng)論 0 1
  • 其他有關(guān)插件化的文章歡迎大家觀閱插件化踩坑之路——Small和Atlas方案對(duì)比Android插件化基礎(chǔ)篇—— c...
    小之丶閱讀 1,919評(píng)論 1 7

友情鏈接更多精彩內(nèi)容