Java 程式執行經歷「源碼編譯 → 類加載 → 類執行」三階段。類加載機制決定類何時、如何進入 JVM 記憶體,是理解熱部署、類隔離、初始化順序等進階主題的基石。
Class 物件<clinit>() (靜態變數賦值、靜態區塊)關鍵點:加載/驗證/準備/解析為「類加載」階段;初始化才真正執行 Java 代碼。
<JAVA_HOME>/lib 核心庫 (rt.jar),無父加載器<JAVA_HOME>/lib/ext 擴展庫ClassLoader.getSystemClassLoader()protected Class> loadClass(String name, boolean resolve) {
// 1. 檢查已加載
Class> c = findLoadedClass(name);
if (c == null) {
try {
// 2. 委派父加載器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name); // Bootstrap
}
} catch (ClassNotFoundException e) {
// 3. 父加載失敗,自己嘗試
c = findClass(name);
}
}
if (resolve) resolveClass(c);
return c;
}
優點:避免重複加載、保護核心類不被竄改 (如 java.lang.String)、層級隔離。
破壞委派:JNDI、JDBC、熱部署 (Tomcat WebappClassLoader 先自載再委派)、OSGi。
主動引用 (7 種):
new、反射 Class.forName()、反序列化建立實例main 方法的啟動類MethodHandle 解析 REF_getStatic/REF_putStatic/REF_invokeStaticjava.lang.invoke 相關被動引用 (不觸發初始化):
new MyClass[10]static final 常數 (編譯期常量池內聯)class Parent {
static { System.out.println("Parent static block"); }
static int a = 10;
}
class Child extends Parent {
static { System.out.println("Child static block"); }
static int b = 20;
}
public class Test {
public static void main(String[] args) {
System.out.println(Child.a); // 只初始化 Parent
// System.out.println(Child.b); // 初始化 Parent → Child
}
}
// 輸出: Parent static block → 10
| 陷阱 | 原因 | 解決 |
|---|---|---|
| 靜態變數循環依賴 | A 初始化呼叫 B 靜態方法,B 又存取 A 靜態變數 | 避免靜態初始化邏輯複雜化、延遲初始化 |
| 類加載器隔離導致 ClassCastException | 同一類被不同加載器載入,視為不同類型 | 理解雙親委派、統一加載器、OSGi/模組化 |
| 熱部署類不更新 | 舊類加載器未卸載、雙親委派優先載入舊版 | 自定義加載器打破委派、框架支援 (Spring DevTools) |
| final 常數值變更未生效 | 編譯期內聯到常量池,需重新編譯所有引用方 | 非真正常數勿用 final、或全量重編譯 |
深入理解類加載機制,是排查 ClassNotFoundException、NoClassDefFoundError、LinkageError 等疑難雜症的關鍵能力。