什麼是類加載器?
負責讀取 .class 位元組碼流,轉換為 java.lang.Class 實例。每個 Class 物件隱含對其加載器的引用 (getClassLoader())。
類加載器決定類的「相同性」
同一全限定名 + 同一類加載器 = 同一類。不同加載器載入同樣位元組碼,得到的 Class 物件 不相等 (equals()/isInstance()/instanceof 皆為 false)。這是隔離機制基礎 (如 Tomcat Webapp 隔離)。
四大類加載器層級
| 加載器 | 類型 | 載入範圍 |
| Bootstrap ClassLoader | C++ 實作 (啟動類) | JAVA_HOME/lib、-Xbootclasspath |
| Extension ClassLoader | Java 實作 | JAVA_HOME/lib/ext、java.ext.dirs |
| Application ClassLoader | Java 實作 (系統類) | ClassPath (-cp/CLASSPATH) |
| Custom ClassLoader | 用戶繼承 ClassLoader | 自定義來源 (檔案/網路/資料庫/加密) |

雙親委派模型
流程
- 收到載入請求 → **委派給父加載器**
- 遞歸向上至 Bootstrap
- 父無法載入 (搜尋範圍無該類) → 子嘗試自行載入
核心優勢
- 避免重複載入 (單一實例)
- 保護核心 API 不被竄改 (無法自定義
java.lang.String)
- 確保類在 JVM 中唯一身份
打破雙親委派
- 覆寫
loadClass() (JDK 1.2 前唯一方式)
- Tomcat/WebLogic 等容器為實現 Webapp 隔離、JSP 熱部署、模組化 (OSGi/Jigsaw) 均打破委派
類加載完整生命週期 (7 階段)

- 加載:獲取位元組碼 → 方法區數據結構 → 堆建立 Class 物件
- 驗證:檔案格式/元資料/位元組碼/符號引用 四層校驗
- 準備:靜態欄位分配記憶體 + **零值** (非初始值)
- 解析:符號引用 → 直接引用 (可延遲至初始化後)
- 初始化:執行
<clinit>() (靜態賦值 + static {}) → 父類優先 → 多線程單次執行保證
- 使用
- 卸載
初始化觸發時機 (5 條規範)
new/靜態欄位讀寫/靜態方法調用 (final 常數除外)
- 反射調用 (
Class.forName、Method.invoke)
- 初始化子類觸發父類初始化
- JVM 啟動主類
MethodHandle 解析靜態句柄
<clinit>() 關鍵特性
- 由編譯器自動收集靜態賦值語句與 static 區塊,**按源碼順序**
- 靜態區塊只能訪問定義在它**之前**的靜態變數 (賦值可、讀取不可)
- 父類
<clinit>() 必先於子類完成 (JVM 保證)
- 多線程並發初始化同一類 → 僅一線程執行,其餘阻塞等待 (利用此特性實現線程安全單例)
自定義類加載器實作範本
public class FileSystemClassLoader extends ClassLoader {
private final String rootDir;
public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; }
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassBytes(name);
if (classData == null) throw new ClassNotFoundException(name);
return defineClass(name, classData, 0, classData.length);
}
private byte[] loadClassBytes(String name) {
String path = rootDir + File.separator + name.replace('.', File.separatorChar) + ".class";
try (InputStream is = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buf = new byte[4096];
int len;
while ((len = is.read(buf)) != -1) baos.write(buf, 0, len);
return baos.toByteArray();
} catch (IOException e) { return null; }
}
// 測試
public static void main(String[] args) throws Exception {
ClassLoader cl = new FileSystemClassLoader("D:/classes");
Class> c = cl.loadClass("com.example.Demo");
System.out.println(c.newInstance());
}
}
⚠️ **遵守委派規範請覆寫 findClass()**;**破壞委派請覆寫 loadClass()**。
熱部署核心原理
- 建立**專用 ClassLoader** 載入需熱更新的類
- 檢測
.class 檔案變更 (監聽/輪詢/文件系統事件)
- **廢棄舊 ClassLoader** (讓其 GC)、**建立新 ClassLoader** 重新載入
- 舊實例持有舊 Class,新實例用新 Class → 實現無重啟更新
挑戰:靜態狀態遷移、資源關閉、執行緒安全、類身份改變導致 instanceof 失效、序列化不相容。
參考資源
- IBM DeveloperWorks: 深入理解 Java 類加載器
- 《深入理解 Java 虛擬機器》第 7 章
- JVM 規範 Chapter 5: Loading, Linking, and Initializing
- 上一篇文章: 並發編程基石:深入理解對象組合與線程安全類設計
- 下一篇文章: Hibernate「Could not find a getter」異常完整解析與解決方案
Copyright ©
程式碼之家 All Rights Reserved