類加載機制是 Java 語言動態性的基石,也是 JVM 運行時核心子系統之一。本文系統梳理 ClassLoader 體系架構、雙親委派模型原理、自定義類加載器實現,以及完整的類加載生命周期,幫助開發者構建完整的類加載知識體系。
ClassLoader 的基本使命:根據類的全限定名查找或生成對應的字節碼,並將其轉換為 java.lang.Class 實例。關鍵方法包括:
loadClass(String name):加載類的入口,實現雙親委派邏輯findClass(String name):查找類字節碼,子類重寫以實現自定義加載邏輯defineClass(byte[] b, int off, int len):將字節碼轉換為 Class 對象(final 方法)getParent():獲取父類加載器getResource(String name) / getResourceAsStream(String name):資源加載Java 類加載器分為系統提供與用戶自定義兩大類,形成清晰的父子層級:
JAVA_HOME/jre/lib/rt.jar 等核心類庫ClassLoader,Java 代碼無法直接獲取引用(返回 null)sun.misc.Launcher$ExtClassLoader(Java 代碼)JAVA_HOME/jre/lib/ext/*.jar 目錄下的擴展庫sun.misc.Launcher$AppClassLoaderCLASSPATH 環境變量加載應用程序類ClassLoader.getSystemClassLoader()java.lang.ClassLoader 並重寫 findClass()public class TestClassLoader {
@Test
public void test() {
// 應用類加載器
System.out.println(ClassLoader.getSystemClassLoader());
// 擴展類加載器
System.out.println(ClassLoader.getSystemClassLoader().getParent());
// 引導類加載器(返回 null)
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
}
}
輸出結果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
null
說明:ClassLoader 類本身由 AppClassLoader 加載,其父加載器為 ExtClassLoader,頂層引導類加載器因 C++ 實現無法在 Java 層獲取。
當某類加載器收到加載請求時:
防止用戶自定義類冒充核心類(如自寫 java.lang.String)。因引導類加載器優先加載 rt.jar 中的 String,用戶類永遠無機會被加載,從根本上杜絕核心 API 被篡改風險。
與雙親委派相反,代理模式採用「先嘗試自我加載,失敗再向上委派」。典型應用:Tomcat 為實現 Web 應用隔離,每個 Webapp 擁有專屬 WebappClassLoader,優先加載 WEB-INF/classes 和 WEB-INF/lib,打破雙親委派,實現同一容器不同應用使用同類庫不同版本。
public class MyClassLoader extends ClassLoader {
private String rootPath;
public MyClassLoader(String rootPath) {
this.rootPath = rootPath;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
// 1. 檢查是否已加載
Class> c = findLoadedClass(name);
if (c != null) return c;
// 2. 讀取字節碼
byte[] classData = getClassData(name);
if (classData == null) throw new ClassNotFoundException();
// 3. 定義類
return defineClass(name, classData, 0, classData.length);
}
private byte[] getClassData(String className) {
String path = rootPath + "/" + className.replace('.', '/') + ".class";
try (InputStream is = new FileInputStream(path);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
// 測試類
package testOthers;
public class HelloWorld {}
// 測試代碼
public class TestMyClassLoader {
@Test
public void test() throws Exception {
MyClassLoader loader = new MyClassLoader("D:");
Class> c = loader.loadClass("testOthers.HelloWorld");
System.out.println(c.getClassLoader()); // 輸出 MyClassLoader 實例
}
}
輸出確認 HelloWorld 由自定義 MyClassLoader 加載,實現了加載源的完全可控。
JVM 將類加載劃分為三大階段、五個細分步驟:
java.lang.Class 對象作為訪問入口| 子階段 | 核心動作 |
|---|---|
| 驗證 | 確保字節流符合 JVM 規範、無安全隱患(文件格式、元數據、字節碼、符號引用驗證) |
| 準備 | 為 靜態變量 分配內存並設置默認值(如 static int i = 5,準備階段 i=0,初始化階段 i=5) |
| 解析 | 將常量池符號引用轉換為直接引用(指向內存地址的指針/偏移量) |
關鍵區別:類常量池(每類一份,存字面量+符號引用)≠ 字符串常量池(全局共享,存字符串對象引用)。
() :賦值靜態變量正確初始值、執行靜態代碼塊() 先於子類() (只有真正使用時才初始化)new、反射、克隆、反序列化)final 常量除外,編譯期已入常量池)Class.forName()MethodHandle 解析 REF_getStatic/REF_putStatic/REF_invokeStatic類加載機制通過層級隔離(加載器樹)、委派協作(雙親委派)、生命周期管控(五階段),構建了 Java 動態擴展與安全穩定的雙重保障。深入理解該機制,是掌握熱部署、模塊化(OSGi/JPMS)、框架原理(Spring/Tomcat 類隔離)、排查 ClassNotFoundException/NoClassDefFoundError/LinkageError 等疑難雜症的必經之路。