Java 類從 `.class` 位元組碼檔案到 JVM 可執行狀態,再到最終被卸載,經歷了一個嚴格規範的生命週期。理解這個過程,不僅是面試必考題,更是排查 `ClassNotFoundException`、`NoClassDefFoundError`、`ClassCircularityError`、靜態欄位初始化順序等疑難雜症的關鍵。本文依據 JVM 規範 (JVMS §5),結合 HotSpot 實作細節,系統梳理七大階段、觸發條件、線程安全機制與實戰陷阱。
┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Loading │──▶│ Linking │ │ Initialization │▶│ Using │▶│ Unloading │
│ (載入) │ │ (連結) │ │ (初始化) │ │ (使用) │ │ (卸載) │
└─────────────┘ └──────┬──────┘ └──────────────┘ └─────────────┘ └─────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Verification Preparation Resolution
(驗證) (準備) (解析)
⚡ -Xverify:none 可關閉大部分驗證(生產環境不建議)。
static 修飾的字段(類變數),不含實例字段。static final **編譯期常數**(基本型別/String,`ConstantValue` 屬性)在此階段直接賦為**代碼指定值**。public static int a = 10; // 準備階段 a=0 → 初始化階段 a=10
public static final int B = 20; // 準備階段 B=20 (ConstantValue)
public static final Object C = new Object(); // 準備階段 C=null → 初始化階段 new Object()
new 實例化對象static 字段(final 編譯期常數除外,已在準備階段賦值)static 方法⚠️ 除此之外(如訪問父類靜態欄位、定義陣列、使用泛型類型參數)屬「被動使用」,**不觸發初始化**。
static {} 靜態塊與靜態字段賦值語句。 由 JVM **隱式同步**(鎖住 Class 物件),多線程並發初始化時**只有一個線程執行**,其餘阻塞等待。class A { static { System.out.println("A init"); B.value = 10; } static int value; }
class B { static { System.out.println("B init"); A.value = 20; } static int value; }
// main: A.value = 1; → 輸出 A init → B init → 完成
// 若 main: B.value = 1; → 輸出 B init → A init → 完成
// 關鍵:JVM 檢測到正在初始化的類再次被初始化請求,直接返回,避免死循環,但可能讀到「零值」。
new 指令(最常見)Class.newInstance() / `Constructor.newInstance()`(反射)clone()(淺拷貝,不調用建構子)ObjectInputStream.readObject()(反序列化,不調用建構子)finalize():**已棄用 (Java 9)**,不保證執行時間/順序/線程,拋出異常會被忽略。**嚴禁依賴它釋放資源**,請用 `try-with-resources` / `Cleaner` / `PhantomReference`。| 現象 | 可能階段 | 排查方向 |
|---|---|---|
| ClassNotFoundException | 載入 | 類路徑缺失、類載入器層級錯誤、動態載入名稱拼寫 |
| NoClassDefFoundError | 載入/連結/初始化 | 編譯時存在但運行時缺失;查看 Cause: • ClassCircularityError (繼承循環) • ExceptionInInitializerError ( • OutOfMemoryError (Metaspace/堆) |
| 靜態欄位值不符預期 | 準備/初始化 | final 常數 vs 非常數、父類初始化順序、循環初始化讀零值 |
| 類卸載不生效 (Metaspace OOM) | 卸載 | 類載入器洩漏(ThreadLocal、緩存、JDBC 驅動、日誌框架持有 Class 引用) |
| LinkageError / IncompatibleClassChangeError | 解析/驗證 | 同一類多載入器重複載入、庫版本衝突 (AOT/模組化邊界) |
Java 類生命週期是 **JVM 類載入子系統** 的核心契約。掌握 載入→驗證→準備→解析→初始化→使用→卸載 各階段的**執行動作、觸發條件、線程安全保證、異常表現**,能讓你從「會用」進階到「懂原理、能調優、會排查」。下次遇到 `NoClassDefFoundError` 時,請順著這條主線思考:是載入找不到?連結驗證失敗?還是 `