JVM 將類加載分為三個階段(嚴格順序):
java.lang.Class 對象作為訪問入口。int 為 0)。<clinit>(),賦值靜態變量、執行靜態代碼塊。| 加載器 | 加載範圍 | 父加載器 | 備註 |
|---|---|---|---|
| Bootstrap ClassLoader | JAVA_HOME/lib、-Xbootclasspath 指定路徑下的核心庫 (rt.jar 等) | 無 (C++ 實現) | String.class.getClassLoader() == null |
| Extension ClassLoader | JAVA_HOME/lib/ext、java.ext.dirs 路徑 | Bootstrap | 繼承 URLClassLoader |
| Application/System ClassLoader | Classpath (-cp、java.class.path) | Extension | 默認應用類加載器,ClassLoader.getSystemClassLoader() |
| 用戶自定義 ClassLoader | 任意來源 (網絡、數據庫、加密文件、動態生成) | 默認 System | 繼承 ClassLoader 並重寫 findClass |
流程:子加載器收到加載請求 → 委派給父加載器 → 遞歸至頂層 Bootstrap → 父加載器無法加載時,子加載器嘗試自行加載。
優勢:
java.lang.String 等)。破壞雙親委派的場景:
Thread.currentThread().getContextClassLoader()) 逆向委派。loadClass:不推薦,極易導致核心類加載失敗(如無法加載 java.lang.Object)。父加載器加載的類對子加載器可見;子加載器加載的類對父加載器不可見。OSGi 利用此特性實現同一類多版本共存。
同一類加載器實例中,每個類只加載一次。但不同加載器可加載同名類,它們在 JVM 內部是不同類型(ClassA != ClassB,實例互轉拋 ClassCastException)。
findClasspublic class MyClassLoader extends ClassLoader {
private final String basePath;
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) {
// 從文件、網絡、數據庫讀取字節碼
// 示例:讀取 basePath/name.class
return Files.readAllBytes(Paths.get(basePath, name.replace('.', '/') + ".class"));
}
}
loadClass 破壞委派邏輯 → 核心類加載失敗。null → 父加載器變為 Bootstrap,無法加載 ext 目錄類。defineClass 直接返回 → 類未真正定義。-verbose:class 或 ClassLoader.getResource("com/example/MyClass.class")。LinkageError / ClassCastException,用 obj.getClass().getClassLoader() 對比。Class.forName("name", true, loader) 指定加載器並觸發初始化;loader.loadClass("name") 僅加載不初始化。System.getProperty("java.class.path"); // 應用類路徑
((URLClassLoader)ClassLoader.getSystemClassLoader()).getURLs(); // 系統加載器 URL
((URLClassLoader)ClassLoader.getSystemClassLoader().getParent()).getURLs(); // 擴展加載器 URL
理解類加載機制是排查 ClassNotFound、NoClassDefFound、LinkageError、熱部署失效、模塊隔離等疑難雜癥的基石。遵循「重寫 findClass、不破壞委派、明確父加載器」原則,配合線程上下文類加載器處理 SPI,即可駕馭複雜類加載場景。