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

Java ClassLoader 機制深度解析:雙親委派模型與自定義類加載器

     編輯:Java 相關知識

ClassLoader 核心使命

將 .class 位元組碼載入 JVM 方法區,生成 java.lang.Class 實例,供程式使用。載入時機:靜態(new、static 存取)或動態(Class.forName()、ClassLoader.loadClass())。

三大內建類加載器(啟動階層)

加載器實現語言載入範圍父加載器
Bootstrap ClassLoaderC++ (JVM 內建)<JAVA_HOME>/lib 核心庫 (rt.jar 等)null (頂層)
Extension ClassLoaderJava<JAVA_HOME>/lib/ext 擴展庫Bootstrap
Application ClassLoaderJavaClasspath (-cp) 指定目錄/JARExtension

JVM 啟動 → Bootstrap 載入核心 API → Extension 載入擴展 → AppClassLoader 載入應用類 → 用戶線程運行。

雙親委派模型

protected synchronized Class loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
    Class c = findLoadedClass(name); // 1. 快取檢查
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false); // 2. 委派父加載器
            } else {
                c = findBootstrapClassOrNull(name); // 3. 委派 Bootstrap
            }
        } catch (ClassNotFoundException e) {
            c = findClass(name); // 4. 自己載入
        }
    }
    if (resolve) resolveClass(c);
    return c;
}

流程圖解

  1. 檢查是否已載入(快取)。
  2. 有父加載器 → 委派 parent.loadClass();無父加載器 → 委派 Bootstrap。
  3. 父加載器失敗(拋 ClassNotFoundException) → 呼叫自身 findClass() 載入。
  4. resolve 為 true 時進行連結(驗證、準備、解析)。

為什麼要雙親委派?

  1. 避免重複載入: 父已載入則直接返回,子不再重複工作。
  2. 核心類庫安全: 防止惡意代碼自定義 java.lang.String、java.lang.System 等核心類竄改行為。Bootstrap 優先載入官方類,用戶類永無機會覆蓋。

打破雙親委派的場景

  • JNDI/JDBC/SPI: Bootstrap 載入介面,需反向委派 Thread Context ClassLoader 載入廠商實現(ServiceLoader 機制)。
  • 熱部署/模組化: Tomcat、OSGi、Spring Boot DevTools 使用自定義 ClassLoader 隔離應用類,支援重載、多版本共存。
  • 加密/混淆類: 自定義 ClassLoader 解密位元組碼再 defineClass。

自定義類加載器實作範本

public class MyClassLoader extends ClassLoader {
    private final String basePath; // 類文件根目錄或 URL

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

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name); // 從檔案/網路/資料庫讀取位元組碼
        if (classData == null) throw new ClassNotFoundException(name);
        return defineClass(name, classData, 0, classData.length); // 核心:定義類
    }

    private byte[] loadClassData(String name) {
        String path = basePath + name.replace('.', '/') + ".class";
        try (InputStream is = new FileInputStream(path);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buf = new byte[1024]; int len;
            while ((len = is.read(buf)) != -1) baos.write(buf, 0, len);
            return baos.toByteArray();
        } catch (IOException e) { return null; }
    }
}

使用:new MyClassLoader("D:/myclasses/").loadClass("com.demo.MyService")。

Class.forName vs ClassLoader.loadClass

方法簽名是否初始化典型用途
Class.forNameforName(String name)是 (執行 static 區塊)JDBC Driver 註冊、SPI 載入
Class.forNameforName(name, initialize, loader)可控制精確控制初始化時機
ClassLoader.loadClassloadClass(String name)否 (resolve=false)僅載入不觸發初始化

關鍵差異: JDBC Driver 需在 static 區塊呼叫 DriverManager.registerDriver(),必須用 Class.forName() 觸發初始化;若用 loadClass() 則不會註冊,導致 No suitable driver。

連結階段三步驟

  1. 驗證: 位元組碼格式、語義、符號引用合法性。
  2. 準備: 為 static 欄位分配記憶體、賦予預設值(0/false/null)。
  3. 解析: 將常量池符號引用轉為直接引用(指標/偏移量)。

總結

ClassLoader 是 Java 動態性的基石。掌握 雙親委派流程、三大內建加載器分工、打破委派的典型場景、自定義加載器寫法、forName vs loadClass 初始化語義差異,即可駕馭類隔離、熱插拔、加密保護、框架 SPI 等進階主題。源碼閱讀建議從 java.lang.ClassLoader 與 sun.misc.Launcher 入手。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved