天天看點

ClassLoader 學習筆記

文章目錄

  • ​​1. ClassLoader 是做什麼的?​​
  • ​​2. JVM 類加載流程​​
  • ​​3. 延遲加載​​
  • ​​4. ClassLoader 傳遞性​​
  • ​​5. 雙親委派​​
  • ​​6. Class.forName​​
  • ​​7. 自定義加載器​​
  • ​​Class.forName` vs `ClassLoader.loadClass​​
  • ​​參考連結​​

1. ClassLoader 是做什麼的?

用來加載 Class 的。負責将 Class 的位元組碼形式轉換成 記憶體中的 Class 對象,位元組碼可以來自于磁盤檔案 *.class,也可以是 jar 包裡的 *.class,也可以是 ​

​.dex​

​​ 中的 class,位元組碼的本質就是一個位元組數組 ​

​[]byte​

​,它有特定的複雜的内部格式。

ClassLoader 學習筆記

每個 ​

​Class​

​​ 對象的内部都有一個 ​

​classLoader​

​​ 字段來辨別自己是由哪個 ​

​ClassLoader​

​​ 加載的。​

​ClassLoader​

​​ 就像一個容器,裡面裝了很多已經加載的 ​

​Class​

​ 對象。

class Class<T> {
  ...
  private final ClassLoader classLoader;
  ...
}
      

2. JVM 類加載流程

Java語言系統自帶有三個類加載器:

  • ​Bootstrap ClassLoader​

    ​​ 負責加載 ​

    ​JVM​

    ​ 運作時核心類,這些類位于 ​

    ​$JAVA_HOME/lib/rt.jar​

    ​ 檔案中,我們常用内置庫 ​

    ​java.xxx.*​

    ​ 都在裡面,比如 ​

    ​java.util.​

    ​、java.io.​

    ​、java.nio.​

    ​、​

    ​java.lang.​

    ​ 等等。這個 ​

    ​ClassLoader​

    ​ 比較特殊,它是由 ​

    ​C​

    ​ 代碼實作的,我們将它稱之為「根加載器」。
  • ​Extention ClassLoader​

    ​​負責加載 ​

    ​JVM​

    ​ 擴充類,比如 ​

    ​swing​

    ​ 系列、内置的 ​

    ​js​

    ​ 引擎、​

    ​xml​

    ​ 解析器 等等,這些庫名通常以 ​

    ​javax​

    ​ 開頭,它們的 ​

    ​jar​

    ​ 包位于 ​

    ​$JAVA_HOME/lib/ext/*.jar​

    ​ 中,有很多 ​

    ​jar​

    ​ 包。
  • ​Appclass Loader​

    ​​ 才是直接面向我們使用者的加載器,它會加載 ​

    ​Classpath​

    ​ 環境變量裡定義的路徑中的 ​

    ​jar​

    ​ 包和目錄。我們自己編寫的代碼以及使用的第三方 jar 包通常都是由它來加載的。

​AppClassLoader​

​​ 可以由 ​

​ClassLoader​

​​ 類提供的靜态方法 ​

​getSystemClassLoader()​

​​ 得到,它就是我們所說的「系統類加載器」,我們使用者平時編寫的類代碼通常都是由它加載的。當我們的 ​

​main​

​​ 方法執行的時候,這第一個使用者類的加載器就是 ​

​AppClassLoader​

​。

3. 延遲加載

​JVM​

​ 運作并不是一次性加載所需要的全部類的,它是按需加載,也就是延遲加載。程式在運作的過程中會逐漸遇到很多不認識的新類,這時候就會調用 ​

​ClassLoader​

​​ 來加載這些類。加載完成後就會将 ​

​Class​

​​ 對象存在 ​

​ClassLoader​

​ 裡面,下次就不需要重新加載了。

比如你在調用某個類的靜态方法時,首先這個類肯定是需要被加載的,但是并不會觸及這個類的執行個體字段,那麼執行個體字段的類别 ​

​Class​

​ 就可以暫時不必去加載,但是它可能會加載靜态字段相關的類别,因為靜态方法會通路靜态字段。而執行個體字段的類别需要等到你執行個體化對象的時候才可能會加載。

4. ClassLoader 傳遞性

程式在運作過程中,遇到了一個未知的類,它會選擇哪個 ​

​ClassLoader​

​​ 來加載它呢?虛拟機的政策是使用調用者 ​

​Class​

​​ 對象的 ​

​ClassLoader​

​​ 來加載目前未知的類。何為調用者 ​

​Class​

​​ 對象?就是在遇到這個未知的類時,虛拟機肯定正在運作一個方法調用(靜态方法或者執行個體方法),這個方法挂在哪個類上面,那這個類就是調用者 ​

​Class​

​​ 對象。前面我們提到每個 ​

​Class​

​對象裡面都有一個 classLoader 屬性記錄了目前的類是由誰來加載的。

因為 ​

​ClassLoader​

​ 的傳遞性,所有延遲加載的類都會由初始調用 ​

​main​

​ 方法的這個 ​

​ClassLoader​

​ 全全負責,它就是 ​

​AppClassLoader​

​。

5. 雙親委派

前面我們提到 ​

​AppClassLoader​

​​ 隻負責加載 ​

​Classpath​

​​ 下面的類庫,如果遇到沒有加載的系統類庫怎麼辦,​

​AppClassLoader​

​​ 必須将系統類庫的加載工作交給 ​

​BootstrapClassLoader​

​​ 和 ​

​ExtensionClassLoader​

​ 來做,這就是我們常說的「雙親委派」。

ClassLoader 學習筆記

​AppClassLoader​

​​ 在加載一個未知的類名時,它并不是立即去搜尋 ​

​Classpath​

​​,它會首先将這個類名稱交給 ​

​ExtensionClassLoader​

​​ 來加載,如果 ​

​ExtensionClassLoader​

​​ 可以加載,那麼 ​

​AppClassLoader​

​​ 就不用麻煩了。否則它就會搜尋 ​

​Classpath​

​。

而 ​

​ExtensionClassLoader​

​​ 在加載一個未知的類名時,它也并不是立即搜尋 ​

​ext​

​​ 路徑,它會首先将類名稱交給 ​

​BootstrapClassLoader​

​​ 來加載,如果 ​

​BootstrapClassLoader​

​​ 可以加載,那麼 ​

​ExtensionClassLoader​

​​ 也就不用麻煩了。否則它就會搜尋 ​

​ext​

​​ 路徑下的 ​

​jar​

​ 包。

這三個 ​

​ClassLoader​

​​ 之間形成了級聯的父子關系,每個 ​

​ClassLoader​

​​ 都很懶,盡量把工作交給父親做,父親幹不了了自己才會幹。每個 ​

​ClassLoader​

​對象内部都會有一個 parent 屬性指向它的父加載器。

class ClassLoader {
  ...
  private final ClassLoader parent;
  ...
}
      

值得注意的是圖中的 ​

​ExtensionClassLoader​

​​ 的 ​

​parent​

​​ 指針畫了虛線,這是因為它的 ​

​parent​

​​ 的值是 ​

​null​

​,當 ​

​parent​

​ 字段是 ​

​null​

​ 時就表示它的父加載器是「根加載器」。如果某個 ​

​Class​

​​ 對象的 ​

​classLoader​

​​ 屬性值是​

​null​

​​,那麼就表示這個類也是「根加載器」加載的。注意這裡的 ​

​parent​

​​ 不是 ​

​super​

​​ 不是父類,隻是 ​

​ClassLoader​

​内部的字段。

6. Class.forName

​forName​

​​ 方法使用調用者 ​

​Class​

​​ 對象的 ​

​ClassLoader​

​​ 來加載目标類。不過 ​

​forName​

​​ 還提供了多參數版本,可以指定使用哪個 ​

​ClassLoader​

​ 來加載。

Class<?> forName(String name, boolean initialize, ClassLoader cl)      

通過這種形式的 ​

​forName​

​ 方法可以突破内置加載器的限制,通過使用自定類加載器允許我們自由加載其它任意來源的類庫。根據 ​

​ClassLoader​

​ 的傳遞性,目标類庫傳遞引用到的其它類庫也将會使用自定義加載器加載。

​​Android hotfix 中的應用​​;

/**
     * 通過反射擷取BaseDexClassLoader對象中的PathList對象
     *
     * @param baseDexClassLoader BaseDexClassLoader對象
     * @return PathList對象
     */
    public static Object getPathList(Object baseDexClassLoader)
            throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException, ClassNotFoundException {
        return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }      

7. 自定義加載器

​ClassLoader​

​​ 裡面有三個重要的方法 ​

​loadClass()​

​​、​

​findClass()​

​​ 和 ​

​defineClass()​

​。

  • ​loadClass()​

    ​​ 方法是加載目标類的入口,它首先會查找目前​

    ​ClassLoader​

    ​​ 以及它的雙親裡面是否已經加載了目标類,如果沒有找到就會讓雙親嘗試加載,如果雙親都加載不了,就會調用​

    ​findClass()​

    ​ 讓自定義加載器自己來加載目标類。
  • ​findClass()​

    ​ 方法是需要子類來覆寫的,不同的加載器将使用不同的邏輯來擷取目标類的位元組碼。
  • 拿到這個位元組碼之後再調用​

    ​defineClass()​

    ​​ 方法将位元組碼轉換成​

    ​Class​

    ​ 對象。
class ClassLoader {

  // 加載入口,定義了雙親委派規則
  Class loadClass(String name) {
    // 是否已經加載了
    Class t = this.findFromLoaded(name);
    if(t == null) {
      // 交給雙親
      t = this.parent.loadClass(name)
    }
    if(t == null) {
      // 雙親都不行,隻能靠自己了
      t = this.findClass(name);
    }
    return t;
  }
  
  // 交給子類自己去實作
  Class findClass(String name) {
    throw ClassNotFoundException();
  }
  
  // 組裝Class對象
  Class defineClass(byte[] code, String name) {
    return buildClassFromCode(code, name);
  }
}

class CustomClassLoader extends ClassLoader {

  Class findClass(String name) {
    // 尋找位元組碼
    byte[] code = findCodeFromSomewhere(name);
    // 組裝Class對象
    return this.defineClass(code, name);
  }
}
      

Class.forNamevsClassLoader.loadClass

Class<?> x = Class.forName("[I");
System.out.println(x);

x = ClassLoader.getSystemClassLoader().loadClass("[I");
System.out.println(x);

---------------------
class [I

Exception in thread "main" java.lang.ClassNotFoundException: [I
...      

參考連結

  • ​​老大難的 Java ClassLoader 再不了解就老了​​
  • ​​一看你就懂,超詳細java中的ClassLoader詳解​​
  • ​​深入了解JVM之ClassLoader​​