ClassLoader之雙親委托機制

一、前言

無論是做Java開發(fā),還是Android開發(fā),ClassLoader是少不了打交道的。即便我們不圍繞它做點啥,它也默默的在為我們做著事情。
顧名思義,ClassLoader就是Java編譯成Class文件后,通過它加載到JVM中來運行的。

Android本身有著和Java同源的血統(tǒng),因此,ClassLoader的核心功能機制是不會變的:
IO讀取Class文件,解析,分配棧、堆,加載到永久代(Perm區(qū));
雙親委托尋找 & 加載;

二、ClassLoader的類型和繼承關(guān)系

2.1、Java中的ClassLoader

image.png
  • ClassLoader是一個抽象類,其中定義了ClassLoader的主要功能;
  • SecureClassLoader繼承了抽象類ClassLoader;
    它并不是ClassLoader的實現(xiàn)類,而是拓展了ClassLoader類加入了權(quán)限方面的功能,加強了安全性。
  • URLClassLoader類繼承自SecureClassLoader,用來通過URl路徑從jar文件和文件夾中加載類和資源;
  • ExtClassLoader和AppClassLoader都繼承自URLClassLoader,它們都是Launcher 的內(nèi)部類;
    Launcher 是Java虛擬機的入口應(yīng)用,ExtClassLoader和AppClassLoader都是在Launcher中進行初始化的。

2.2、Android中的ClassLoader

image1.png
  • ClassLoader是一個抽象類,其中定義了ClassLoader的主要功能。BootClassLoader是它的內(nèi)部類;
  • SecureClassLoader繼承了抽象類ClassLoader;
    它并不是ClassLoader的實現(xiàn)類,而是拓展了ClassLoader類加入了權(quán)限方面的功能,加強了安全性。
  • URLClassLoader類繼承自SecureClassLoader,用來通過URl路徑從jar文件和文件夾中加載類和資源;
  • InMemoryDexClassLoader是Android8.0新增的類加載器,繼承自BaseDexClassLoader,用于加載內(nèi)存中的dex文件;
  • BaseDexClassLoader繼承自ClassLoader,是抽象類ClassLoader的具體實現(xiàn)類,PathClassLoader和DexClassLoader都繼承它;

2.3、Android各ClassLoader區(qū)別

  • BootClassLoader,它是 Android 中最頂層的 ClassLoader,繼承自ClassLoader,C代碼編寫,不能被繼承和修改。
  • PathClassLoader:只能加載已經(jīng)安裝到Android系統(tǒng)中的apk文件(/data/app目錄),是Android默認(rèn)使用的類加載器。
  • DexClassLoader:可以加載任意目錄下的dex/jar/apk/zip文件,比PathClassLoader更靈活,是實現(xiàn)熱修復(fù)的重點。

三、類加載機制(雙親委托機制保證類的唯一性)

先來看看 DexClassLoader 和 PathClassLoader,只有繼承,并沒有重載 loadClass 這個類加載方法:

package dalvik.system;
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package dalvik.system;
public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

loadClass 在 BaseDexClassLoader 中也沒有重載,實際的實現(xiàn),還是在 JDK的 ClassLoader 中:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 1. 先檢查該類是否已經(jīng)被當(dāng)前 XxxClassLoader 加載
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                // 2. 如果沒有加載,且有父類加載器,就讓父類重復(fù)該過程
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // 3. 如果沒有找到, 調(diào)用自身查找方法,該方法需要子類Override實現(xiàn),
                // 否則拋異常:ClassNotFoundException
                c = findClass(name);
            }
        }
        return c;
}

protected final Class<?> findLoadedClass(String name) {
    ClassLoader loader;
    if (this == BootClassLoader.getInstance())
        loader = null;
    else
        // DexClassLoader/PathClassLoader -> BaseDexClassLoader -> Native..
        loader = this; 
    return VMClassLoader.findLoadedClass(loader, name);
}

回顧上面3個步驟:

  1. 除了頂層類加載器外,其他的類加載器都有自己的父類加載器,在加載類時首先判斷這個類是否被加載過,如果已經(jīng)加載則直接返回;
  2. 如果未被加載過,則先嘗試讓父加載器進行加載,最終所有加載請求都會傳遞給頂層的加載器中;
  3. 當(dāng)父加載器發(fā)現(xiàn)未找到所需的類而無法完成加載請求時,子加載器的findClass方法中進行加載;

這就是雙親委托機制:當(dāng)未加載類時,永遠先委托父類去查找 / 加載!
不要覺得這是給父類『找麻煩』,這實際上是一種機制:保證一個類的唯一性!
雙親委托機制可以保證同一個全限定名的class,可以被同一個ClassLoader加載!

類的唯一性判斷:
JVM 判定兩個 class 是否相同:

  1. 判斷兩個類名是否相同;
  2. 判斷是否由同一個類加載器實例加載;

只有兩者同時滿足的情況下,JVM 才認(rèn)為這兩個 class 是相同的。
注:同一個class,如果被兩個不同的 ClassLoader 實例所加載,JVM 也會認(rèn)為它們是兩個不同 class。

四、熱修復(fù)技術(shù)

一個APK,可能有一個或多個 dex 文件,如果 classes.dex 和 classes1.dex 有重復(fù)的類,當(dāng)系統(tǒng)加載到這個重復(fù)類時,會如何?
一個 ClassLoader 可以有多個 dex 文件(存在 dexPathList 中),每個 dex 文件是一個 Element,多個 dex 文件排列成一個有序的數(shù)組(dexElements),當(dāng)查找某個類時,會按順序遍歷 dex 文件,如果找到則返回;找不到則繼續(xù)從下一個 dex 中查找。
因此靠前的 dex 文件會優(yōu)先被選擇(多個 dex 中有重復(fù)的類,則最靠前的被加載,后面的不會再被加載,因為有雙親委托機制)。

4.1、基于 dex 分包方案

基于DEX分包方案,使用了多DEX加載的原理。
大致的過程就是:把bug方法修復(fù)以后,放到一個單獨的dex里,插入到dexElements數(shù)組的最前面,讓虛擬機去加載修復(fù)完后的方法。
當(dāng)patch.dex中包含Test.class時就會優(yōu)先加載,在后續(xù)的DEX中遇到Test.class的話就會直接返回而不去加載,這樣就達到了修復(fù)的目的。

詳細步驟如下:
(1)通過獲取到當(dāng)前應(yīng)用的Classloader,即為BaseDexClassloader;
(2)通過反射獲取到他的DexPathList屬性對象pathList;
(3)通過反射調(diào)用pathList的dexElements方法把patch.dex轉(zhuǎn)化為Element[];
(4)兩個Element[]進行合并,把patch.dex放到最前面去;
(5)根據(jù)類的加載機制,加載Element[],達到修復(fù)目的。
特點是產(chǎn)很靈活,對開發(fā)者透明,但是不支持即時生效,必須通過重啟才能生效。另外當(dāng)插入的dex比較多的時候影響啟動性能比較大。

4.2、阿里的 AndFix 方案

AndFix提供了一種運行時在Native修改Filed指針的方式,實現(xiàn)方法的替換,達到即時生效無需重啟,對應(yīng)用無性能消耗的目的。
AndFix對ART設(shè)備同樣支持,具體的過程與Dalvik相似。

替換流程如下:
Dalvik設(shè)備——>Native層找到被替換的類——>將類的狀態(tài)設(shè)置為初始化完畢——>得到新舊方法的指針——>操作指針,指針指向新的替換方法——>完成新的方法替換。
特點是及時性強,不需要重啟手機,生成的差量包小,性能損耗小,缺點是是不支持新增字段,也不支持對資源的替換,和ROM關(guān)系很大,一旦廠商修改ROM下發(fā)就失敗了。

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

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

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