類加載器是 Java 動態性的基石,負責將 `.class` 文件轉化為 JVM 可執行的 `java.lang.Class` 對象。掌握其工作原理,是理解熱部署、模塊隔離、框架原理、ClassLoader 相關疑難雜症的鑰匙。
public class Main {
public static void main(String[] args) {
A a1 = new A(); // 觸發 A 加載
B b1; // 僅聲明,不觸發 B 加載
}
}
// 執行 java -verbose:class Main 可見僅加載 A,未加載 B
條件分支同理:`new` 執行前類不會加載,這是動態插拔、條件裝配的基礎。
// 兩個重載
public static Class> forName(String className)
// 等價於 forName(className, true, callerClassLoader)
// 參數2 initialize=true:加載後執行靜態初始化塊
public static Class> forName(String name, boolean initialize, ClassLoader loader)
// 靈活控制:是否初始化、指定加載器
// 底層均調用 native forName0()
initialize 參數實戰:
// Word 類含 static { System.out.println("static init"); }
Class> c = Class.forName("Word", false, loader); // 僅加載連接,不執行
Object o1 = c.newInstance(); // 首次主動使用觸發初始化,輸出 "static init"
Object o2 = c.newInstance(); // 不再輸出, 僅執行一次 (同一加載器前提)
ClassLoader loader = obj.getClass().getClassLoader(); // 獲取當前類加載器
Class> c = loader.loadClass("Word"); // 僅加載連接,不初始化 (initialize=false)
返回類已連接但未初始化,首次主動使用時觸發 `
// 從指定目錄/JAR/遠程 URL 加載類
URL url = new URL("file:/d:/myapp/plugins/");
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
Class> pluginClass = ucl.loadClass("com.my.Plugin");
Plugin plugin = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
plugin.execute();
支援 `file:`、`http:`、`jar:` 協議,插件化架構核心基礎設施。
Bootstrap Loader (C++ 實現, 無 Java 對象, 父=null)
↑ 父子關係 (非實例關係)
ExtClassLoader (sun.misc.Launcher$ExtClassLoader) ← 加載 jre/lib/ext/*.jar, java.ext.dirs
↑
AppClassLoader (sun.misc.Launcher$AppClassLoader) ← 加載 classpath (-cp/CLASSPATH), 系統默認加載器
↑
Custom ClassLoader (用戶自定義)
驗證代碼:
ClassLoader cl = Test.class.getClassLoader(); // AppClassLoader
System.out.println(cl); // sun.misc.Launcher$AppClassLoader@...
System.out.println(cl.getParent()); // sun.misc.Launcher$ExtClassLoader@...
System.out.println(cl.getParent().getParent()); // null (Bootstrap)
| 加載器 | 路徑來源 | 修改方式 |
|---|---|---|
| Bootstrap | sun.boot.class.path | 僅可通過 `-Xbootclasspath/a:` 追加,`-D` 無效 |
| ExtClassLoader | java.ext.dirs | `-Djava.ext.dirs=path` 或修改 JRE lib/ext 目錄 |
| AppClassLoader | java.class.path | `-cp` / `-classpath` / `CLASSPATH` 環境變量 (優先級:-cp > 環境變量) |
關鍵:系統加載器啟動後路徑固化,運行期 `System.setProperty()` 無法改變其搜索路徑。動態擴展路徑必須創建新 ClassLoader。
規則:加載請求先委派父加載器,父找不到再由自己搜索。
// Test 和 TestLib 同目錄
// 1. 僅在當前目錄 → 皆由 AppClassLoader 加載
// 2. 同時放入 jre/lib/ext/classes → 皆由 ExtClassLoader 加載
// 3. 放入 jre/classes → 皆由 Bootstrap 加載
// 4. Test 在 jre/classes,TestLib 刪除 → Test 加載成功,TestLib 拋 NoClassDefFoundError
// 原因:Test 由 Bootstrap 加載,委派給 Bootstrap 加載 TestLib,找不到且無父加載器
ExtClassLoader 特殊性:掃描 `java.ext.dirs` 下所有子目錄 JAR 與 classes 目錄,而非單一路徑。
public class PluginClassLoader extends ClassLoader {
private final String pluginDir;
public PluginClassLoader(String pluginDir, ClassLoader parent) {
super(parent); // 顯式指定父加載器,默認為獲取此類的加載器
this.pluginDir = pluginDir;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
// 1. 讀取 .class 字節碼 (文件、網絡、加密解密、動態生成)
byte[] classData = loadClassData(name);
if (classData == null) throw new ClassNotFoundException(name);
// 2. 定義類
return defineClass(name, classData, 0, classData.length);
}
private byte[] loadClassData(String name) { /* 讀取 pluginDir/name.class */ }
}
核心要點:
類加載器機制賦予 Java 動態擴展、隔離部署、熱更新能力。深入理解委派模型、命名空間隔離、生命週期管理,是從 API 調用者進階為框架設計者、平台工程師的必經之路。