Java 語言的動態性核心在於「運行期加載、連接、初始化」。類加載機制讓 Java 程式能在運行時動態決定加載哪個實現類,支撐了框架級開發(SPI、熱部署、模塊化)的基石。本文系統梳理類加載的 7 個階段、驗證細節、初始化時機、雙親委派模型及實戰注意點。

完整生命週期:加載 → 驗證 → 準備 → 解析 → 初始化 → 使用 → 卸載。其中驗證、準備、解析統稱為連接(Linking)階段。加載、驗證、準備、初始化、卸載 5 階段順序固定;解析階段可在初始化之後(動態綁定/晚期綁定,如 invokedynamic)。
核心任務「三步走」:
java.lang.Class 對象,作為方法區類數據的訪問入口。注意:數組類不通過類加載器創建,由 JVM 直接生成。加載階段與連接階段可交替進行。
防止惡意/錯誤字節碼破壞 JVM 安全。雖然 Java 編譯器已做大量檢查,但 Class 文件可能來自非 Java 源碼(Kotlin、Scala、Groovy、手工構造等),驗證不可省略。
四階段驗證:
0xCAFEBABE、版本號範圍、常量池 Tag 合法性等。通過後字節流正式進入方法區。-XX:-UseSplitVerifier 關閉分割驗證優化。性能提示:生產環境若確信所有代碼(含三方依賴)經過充分測試,可用
-Xverify:none關閉大部分驗證,縮短啟動時間。
僅針對類變量,在方法區分配內存並設置零值。實例變量不在此階段處理。
| 類型 | 零值 |
|---|---|
| int | 0 |
| long | 0L |
| float | 0.0f |
| double | 0.0d |
| short | (short)0 |
| byte | (byte)0 |
| char | '\u0000' |
| boolean | false |
| reference | null |
關鍵區別:
public static int value = 10; → 準備階段 value = 0;putstatic 指令在 <clinit> 中賦值 10。public static final int VALUE = 10; → 準備階段直接賦值 10(ConstantValue 屬性),編譯期常量折疊進常量池。同一符號引用在同一 JVM 多次解析會緩存結果(常量池標記已解析),invokedynamic 除外(每次必須重新解析,支撐動態語言特性)。
類加載最後階段,真正執行 Java 程式代碼(靜態變量賦值、靜態代碼塊)。
<clinit>,JVM 保證父類先執行完畢(第一個執行的是 java.lang.Object)。<clinit>(字段初始化),但不要求父接口先執行(僅當使用父接口字段時觸發)。new、getstatic、putstatic、invokestatic 字節碼指令(new 對象、讀/寫靜態字段、調用靜態方法)。final 常量除外(編譯期已進常量池,直接讀取)。main())。java.lang.reflect)。MethodHandle 解析結果為 REF_getStatic/REF_putStatic/REF_invokeStatic 且類未初始化。被動引用不觸發初始化:如通過子類引用父類靜態字段、定義對象數組、常量折疊等。
雖未在原文詳細展開,但理解類加載機制必須掌握:
<JAVA_HOME>/lib 下核心庫。<JAVA_HOME>/lib/ext 或 java.ext.dirs 指定目錄。ClassLoader 重寫 findClass(),實現隔離、熱替換、加密解密等。委派流程:子加載器收到請求 → 委派父加載器 → 遞歸至啟動類加載器 → 父加載器無法加載 → 子加載器嘗試加載。打破雙親委派(如 Tomcat、OSGi、JDBC SPI)需重寫 loadClass()。
<clinit> 拋出異常導致 ExceptionInInitializerError,類永久不可用,極難排查。StackOverflowError 或死鎖。--add-opens 或遷移公共 API。類加載機制是 Java 動態性的基石,七大階段環環相扣。驗證保安全、準備分配內存、解析轉引用、初始化執行業務邏輯。雙親委派保證核心類庫安全,打破委派實現框架擴展。掌握初始化觸發時機、<clinit> 執行特性、常量折疊差異,能有效避免線上疑難雜癥。