《Java高併發編程詳解-多線程架構與設計》JVM類加載器

摘自《Java高併發編程詳解-多線程架構與設計》第九章 p158-p176

總結

  1. 內置類加載器 bootstrap ClassLoader,Ext ClassLoader ,App ClassLoader。分別加載jre\lib,jre\lib\ext,-cp或者-classpath對應的classpath

  2. 通過繼承ClassLoader重寫findClass特殊目錄來實現自定義類加載器。特殊目錄或者設置父加載器爲空,讓loadClass時跳過父類加載器(ps,如父類已經加載同名類,父加載器非空則會返回cache)。重寫loadClass可以完全繞過雙親委託。

  3. class的實例是被類加載器的【實例】隔離。(當然不同的類加載器類型也會隔離) 因此代碼中儘量得到同一個classLoader的實例,避免拿不到緩存,反覆 findClass+defineClass。可以設置線程上下文類加載器。

  4. 在不指定parent的情況下,自定義ClassLoader的parent AppClassLoader是指的應用中唯一的AppClassLoader。因此加載classpath下已被加載過的類時,會因調用ClassLoader#getSystemClassLoader()對應AppClassLoader實例去調用findLoadedClass去獲取Class緩存
    在這裏插入圖片描述

ps:loadClass來觀察,而不是使用Class.forName來觀察。Class.forName在loadClass之前應該還有一次獲取緩存的機會。

1.內置三大類加載器

在這裏插入圖片描述

在這裏插入圖片描述

1.1 根加載器 Boostrap ClassLoader

C++編寫
-Xbootclasspath指定根加載器的路徑。

sun.boot.class.path獲得跟加載器加載的資源
jie\lib

在這裏插入圖片描述

1.2 擴展類加載器 Ext ClassLoader

Java編寫,URLClassLoader的子類
用於加載 JAVA_HOME下的jre\lib\ext 裏面的類庫

java.ext.dirs可以獲得擴展類加載器加載的類
jre\lib\ext

在這裏插入圖片描述

也可以將自己的類放到擴展類加載器的位置

在這裏插入圖片描述

1.3 系統類加載器 App ClassLoader

負責加載 -cp/-classpath 指定的類庫資源

在這裏插入圖片描述

2.自定義類加載器

要點

  1. 自定義的類加載器都是ClassLoader的直接或間接在子類

  2. 需要重寫抽象ClassLoader的 findClass方法。

  3. 需要指定一個父類類加載器。若不指定則繞過了雙親委派

  4. 需要自定義一個路徑加載特殊的class,該目錄不能爲已經有類加載器使用過的目錄。
    可以使用loadClass打破雙親委派後,再使用任意路徑。(相同的目錄導致被委託給了父類類加載器加載)

  5. 得到類的二進制數據(無論網絡/本地讀取或動態代理/cglib生存),使用defineClass將其變成Class

在這裏插入圖片描述

案例

/**
 * @auth thewindkee
 * @date 2018/12/15 0015 21:58
 */
public class MyClassLoader extends ClassLoader {
    //public static final String LIB_PATH = "C:\\Users\\gkwind\\Desktop";
    // 默認加載的位置
    public static final String LIB_PATH = "E:\\gkrep\\gitee\\test\\other\\src\\main\\java\\com\\gkwind\\ClassLoader\\demo\\";
    /*
     * !不要定義在bootstrap,ext,app類加載器能加載的位置。
     * 因爲雙親委派的關係,會導致該類加載器失效。
     * 如下定義必須修改loadClass打破雙親委派
     */
    //public static final String LIB_PATH = MyClassLoader.class.getResource("/").getPath();
    public MyClassLoader() {
        //設置爲null可以繞過雙親委派
        //super(null);
    }

    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }


    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            //這裏查找多層
            byte[] bytes = getClassBytes(name);
            //defineClass方法可以把二進制流字節組成的文件轉換爲一個java.lang.Class
            Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
            System.out.println(c);
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }

    private byte[] getClassBytes(String name) throws Exception {
        // 這裏要讀入.class的字節,因此要使用字節流
        String resolvedPath = name.replace(".", "/");
        File file = new File(LIB_PATH + resolvedPath + ".class");
        System.out.println(MyClassLoader.class.getName() + "findClass:" + file);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        //FileInputStream fis = new FileInputStream(file);
        //FileChannel fc = fis.getChannel();
        //WritableByteChannel wbc = Channels.newChannel(baos);
        //ByteBuffer by = ByteBuffer.allocate(1024);
        //while (true) {
        //    int i = fc.read(by);
        //    if (i == 0 || i == -1)
        //        break;
        //    by.flip();
        //    wbc.write(by);
        //    by.clear();
        //}
        //fis.close();
        //wbc.close();
        java.nio.file.Files.copy(file.toPath(), baos);
        return baos.toByteArray();
    }
} 

在這裏插入圖片描述

自定義的類加載器指定特殊目錄或者指定父類加載器爲空,避免雙親委派導致該類加載器失效

classpath 不含有Demo.class
在這裏插入圖片描述

選擇父類ClassLoader未加載的目錄

在這裏插入圖片描述

在這裏插入圖片描述

2.2 雙親委託機制

爲保證基礎類不被破壞。加載類的時候優先祖師爺(遞歸父類)過目(加載)。 ----by 極客時間 jvm

loadClass 規定了雙親委託,所以可以直接重寫loadClass打破雙親委託。
該方法是同步方法 – synchronized (getClassLoadingLock(name))
優先返回已經加載過的同名class --findLoadedClass(name)
loadClass(name,false)
false 指的是不做【連接(linked)階段】的操作。這就是爲什麼加載類,導致類的初始化(【準備階段】)。

在這裏插入圖片描述
在這裏插入圖片描述

★3.繞過雙親委託

不需要刪除class.
1,2都是需要父類未加載過同名類,否則返回cache。3可以重複加載同名類

  1. 繞過系統類加載器,直接使用ext類加載器作爲父類,並傳入特殊的目錄的class
    無cache且父類加載不到特殊目錄,自動到子類去加載。

  2. 設置父類類加載器爲null
    無cache且沒有父類,自動到子類去加載

  3. 重寫loadClass
    loadClass中不再請求父類去加載
    在這裏插入圖片描述

在這裏插入圖片描述

4.類加載命名空間、運行時包、類的卸載

★類加載器命名空間

類加載器作爲一個命名空間,可以隔離class
使用不同類加載器/或者同一加載器的不同實例去加載同一個類,會產生多個實例。
簡單來說,class實例被不同的classLoader實例隔離。

如:★測試類加載器隔離同名類-不同的類加載器實現隔離;在這裏插入圖片描述

使用loadClass來觀察 獲取緩存
在這裏插入圖片描述

在這裏插入圖片描述

運行時包

classloader名+全路徑列明

初始類加載器

一個類的初始類加載器,包含嘗試過加載的所有父類

在這裏插入圖片描述

★測試類加載器隔離同名類-不同的類加載器實現隔離

此處使用了不同的類加載器 去隔離。
Demo.java

不同的位置的Demo.java輸出的內容不同

MyClassLoader對應的java文件
在這裏插入圖片描述

MyClassLoader2對應的另一個class
在這裏插入圖片描述

demo1與demo2中的class 放置的位置
在這裏插入圖片描述

MyClassLoader.java
注意加載class的位置不同!在這裏插入圖片描述

MyClassLoader2.java
在這裏插入圖片描述

測試類
兩個com.gkwind.Demo都被加載成功

在這裏插入圖片描述

類加載器的實例隔離Class對象

驗證:【同一類加載器的不同實例 產生不同Class 實例】
在這裏插入圖片描述

編寫代碼證明

MyClassLoader 繼承ClassLoader
在這裏插入圖片描述
已知
1.Demo.class是自己編譯的Class,不存在於
lib,lib/ext,classpath.在目錄X下
2.A類對應A.class編譯在classpath下。
3.MyClassLoader繼承了ClassLoader,自定義了 findClass的目錄X(App,Ext類加載器無法加載X目錄),沒有重寫loadClass,更沒有打破雙親委派。

問題:爲何MyClassLoader加載Demo的時候,無法通過findLoadedClass獲取Class的Cache?
在這裏插入圖片描述在這裏插入圖片描述

加載A類,注意看兩次new MyClassLoader的parent都是同一個實例。

演示時,造成A類與Demo類不一樣的原因是:classpath對應的AppClassLoader 全局唯一

  • new MyClassLoader().loadClass(“A”)多次,
    委託parent->唯一的AppClassLoader去加載,第一次findClass,第二次findLoadedClass

  • new MyClassLoader().loadClass(“Demo”)多次,由於Demo.class不存在classpath對應的目錄,導致由new MyClassLoader()實例去加載,每次都是新的ClassLoader實例,因此無法從findLoadedClass中獲得緩存的Demo.class

結論:

  1. 儘量保持ClassLoader的唯一,避免不同ClassLoader實例重複加載Class。
  2. 可以通過線程去傳遞ClassLoader。
  3. 【同一類加載器的不同實例 產生不同Class 實例】 正確。—由加載Demo類可以看出。
  4. 繼承ClassLoader的自定義類加載器默認會調用super()傳入默認的AppClassLoader作爲parent。super()傳入唯一的AppClassLoader
    在這裏插入圖片描述
    在這裏插入圖片描述在這裏插入圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章