負責將 .class 位元組碼讀入 JVM,轉為 java.lang.Class 物件。每個 Class 物件持有定義它的 ClassLoader 參考。ClassLoader 本身也是類別,由另一個 ClassLoader 載入,形成樹狀階層。
| 載入器 | 來源 | 實現 | 父載入器 |
|---|---|---|---|
| Bootstrap (啟動) | JRE 核心庫 (rt.jar 等) | Native (C++) | 無 (根節點) |
| Extension (擴展) | jre/lib/ext 目錄 | sun.misc.Launcher$ExtClassLoader | Bootstrap |
| Application/System (系統) | Classpath (-cp) | sun.misc.Launcher$AppClassLoader | Extension |
自訂載入器繼承 java.lang.ClassLoader,父載入器通常是 System ClassLoader。
protected Class> loadClass(String name, boolean resolve) {
// 1. 檢查快取
Class> c = findLoadedClass(name);
if (c == null) {
// 2. 委派父載入器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
// 3. 父載入器找不到,自己找
if (c == null) {
c = findClass(name);
}
}
if (resolve) resolveClass(c);
return c;
}
意義: 確保核心類別(如 java.lang.String)永遠由 Bootstrap 載入,防止惡意代碼偽造核心類別竊取權限。類別身份 = 完整類名 + 定義載入器,同樣位元組碼被不同載入器載入視為不同類別,相互轉型會拋 ClassCastException。
loadClass(name):啟動載入流程(含委派),拋 ClassNotFoundException。findClass(name):實際尋找位元組碼(子類別覆寫此處),找不到拋 ClassNotFoundException。defineClass(name, bytes, off, len):將位元組碼轉為 Class 物件,final 不可覆寫。findLoadedClass(name):查快取。resolveClass(Class):連結階段(驗證、準備、解析)。解決「父載入器無法載入子載入器可見類別」的問題(SPI 機制)。
Thread.getContextClassLoader() / setContextClassLoader()。javax.xml.parsers.DocumentBuilderFactory)由 Bootstrap 載入,實作類(如 Xerces)由 System 載入。Bootstrap 無法委派給子載入器,故在 SPI 代碼中用 Thread.currentThread().getContextClassLoader() 載入實作類。Class.forName("com.mysql.jdbc.Driver") ≡ Class.forName(name, true, currentLoader):初始化類別(執行 static 區塊),用當前類別載入器。Class.forName(name, false, customLoader):指定載入器、不初始化。public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; }
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassData(name);
if (data == null) throw new ClassNotFoundException();
return defineClass(name, data, 0, data.length);
}
private byte[] loadClassData(String className) { /* 讀檔轉 byte[] */ }
}
原則: 只覆寫 findClass,勿覆寫 loadClass,以保留雙親委派邏輯。
從 HTTP 伺服器下載 .class 位元組碼,解密/驗證後 defineClass。客戶端透過介面或反射使用,避免直接引用伺服器端類別。
WebappClassLoader。WEB-INF/classes、WEB-INF/lib,找不到再委派給父載入器(Shared/Common/系統)。java.*、javax.* 等核心套件不納入搜尋範圍,直接委派給 Bootstrap,保護核心類別安全。Export-Package 導出、Import-Package 導入,精確控制類別可見性。Thread.currentThread().setContextClassLoader(bundleClassLoader)。ClassNotFoundException / NoClassDefFoundError 核對清單clazz.getClassLoader()Thread.currentThread().getContextClassLoader()WEB-INF/lib vs Shared/lib?深入理解 ClassLoader,是解決 Java 疑難雜症、實現熱部署、模組化架構的關鍵鑰匙。