JVM 啟動時建立三層類加載器層級:
jre/lib/rt.jar 核心 API。jre/lib/ext 擴展庫,父加載器為 Bootstrap。委派規則:收到加載請求 → 先委派父加載器 → 父加載器找不到 → 子加載器嘗試 findClass()。打破雙親委派需重寫 loadClass()(如 Tomcat、OSGi、JDBC SPI)。
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 1. 檢查是否已加載(快取)
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 2. 委派父加載器
c = parent.loadClass(name, false);
} else {
// 3. 頂層委派 Bootstrap
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 4. 父加載器找不到,自己找
c = findClass(name);
}
}
if (resolve) resolveClass(c);
return c;
}
findLoadedClass:JVM 規範允許 ClassLoader 快取已加載類,避免重複加載。findBootstrapClass0 → findBootstrapClass(native):最終由 C++ 實現的 Bootstrap 加載器完成核心類加載。findClass:預設拋出 ClassNotFoundException,自定義加載器必須重寫此方法。defineClass:將位元組陣列轉為 Class 物件,final 方法不可覆寫。public class MyClassLoader extends ClassLoader {
private final String basePath; // .class 檔案根目錄
public MyClassLoader(String basePath) {
this.basePath = basePath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 1. 先查快取
Class<?> clazz = findLoadedClass(name);
if (clazz != null) return clazz;
// 2. 讀取 .class 位元組碼
byte[] classBytes = loadClassBytes(name);
if (classBytes == null) throw new ClassNotFoundException(name);
// 3. 定義類
return defineClass(name, classBytes, 0, classBytes.length);
}
private byte[] loadClassBytes(String name) {
try {
String path = basePath + File.separator + name.replace('.', File.separatorChar) + ".class";
try (FileInputStream fis = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
} catch (IOException e) {
return null;
}
}
}
public class MainClassLoader {
public static void main(String[] args) throws Exception {
MyClassLoader loader = new MyClassLoader("F:\\OpenLib\\");
Class<?> clazz = loader.findClass("Test");
clazz.getDeclaredConstructor().newInstance(); // 輸出 "Test"
}
}
public class Test {
public Test() { System.out.println("Test"); }
public static void main(String[] args) { System.out.println("Hello World"); }
}
若 ClassA 分別由 Loader1、Loader2 加載,則 ClassA.class.getClassLoader() != ClassA.class.getClassLoader(),兩者 不兼容、無法轉型、static 欄位互不共享。這是 Tomcat 隔離 Webapp、OSGi 模組化、熱部署的核心基礎。
findClass() 即可。JarFile/ZipFile)。loadClass 已 synchronized,findClass 實作需注意並發。