首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java ClassLoader 機制深度解析:雙親委派、源碼追蹤與自定義類加載器實戰

     編輯:Java 相關知識

類加載核心流程:雙親委派模型

JVM 啟動時建立三層類加載器層級:

  1. Bootstrap ClassLoader(C++ 實現):加載 jre/lib/rt.jar 核心 API。
  2. Extension ClassLoader:加載 jre/lib/ext 擴展庫,父加載器為 Bootstrap。
  3. Application ClassLoader:加載 Classpath 應用類,父加載器為 Extension。

委派規則:收到加載請求 → 先委派父加載器 → 父加載器找不到 → 子加載器嘗試 findClass()。打破雙親委派需重寫 loadClass()(如 Tomcat、OSGi、JDBC SPI)。

ClassLoader.loadClass() 源碼深度追蹤(JDK 8)

protected synchronized Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
    // 1. 檢查是否已加載(快取)
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                // 2. 委派父加載器
                c = parent.loadClass(name, false);
            } else {
                // 3. 頂層委派 Bootstrap
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
            // 4. 父加載器找不到,自己找
            c = findClass(name);
        }
    }
    if (resolve) resolveClass(c);
    return c;
}

關鍵點解析

  • findLoadedClass:JVM 規範允許 ClassLoader 快取已加載類,避免重複加載。
  • findBootstrapClass0 → findBootstrapClass(native):最終由 C++ 實現的 Bootstrap 加載器完成核心類加載。
  • findClass:預設拋出 ClassNotFoundException,自定義加載器必須重寫此方法。
  • defineClass:將位元組陣列轉為 Class 物件,final 方法不可覆寫。

自定義類加載器實戰:MyClassLoader

public class MyClassLoader extends ClassLoader {
    private final String basePath; // .class 檔案根目錄

    public MyClassLoader(String basePath) {
        this.basePath = basePath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 1. 先查快取
        Class<?> clazz = findLoadedClass(name);
        if (clazz != null) return clazz;

        // 2. 讀取 .class 位元組碼
        byte[] classBytes = loadClassBytes(name);
        if (classBytes == null) throw new ClassNotFoundException(name);

        // 3. 定義類
        return defineClass(name, classBytes, 0, classBytes.length);
    }

    private byte[] loadClassBytes(String name) {
        try {
            String path = basePath + File.separator + name.replace('.', File.separatorChar) + ".class";
            try (FileInputStream fis = new FileInputStream(path);
                 ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = fis.read(buffer)) != -1) {
                    baos.write(buffer, 0, len);
                }
                return baos.toByteArray();
            }
        } catch (IOException e) {
            return null;
        }
    }
}

測試驗證

public class MainClassLoader {
    public static void main(String[] args) throws Exception {
        MyClassLoader loader = new MyClassLoader("F:\\OpenLib\\");
        Class<?> clazz = loader.findClass("Test");
        clazz.getDeclaredConstructor().newInstance(); // 輸出 "Test"
    }
}

public class Test {
    public Test() { System.out.println("Test"); }
    public static void main(String[] args) { System.out.println("Hello World"); }
}

命名空間隔離:同一個類被不同加載器加載 = 不同類型

若 ClassA 分別由 Loader1、Loader2 加載,則 ClassA.class.getClassLoader() != ClassA.class.getClassLoader(),兩者 不兼容、無法轉型、static 欄位互不共享。這是 Tomcat 隔離 Webapp、OSGi 模組化、熱部署的核心基礎。

常見坑與最佳實踐

  • 不要重寫 loadClass() 除非你完全理解雙親委派破壞後果;重寫 findClass() 即可。
  • 資源關閉:使用 try-with-resources 確保流關閉。
  • 支援 JAR:生產級加載器需支援從 JAR/ZIP 讀取(JarFile/ZipFile)。
  • 線程安全:loadClass 已 synchronized,findClass 實作需注意並發。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved