核心概念釐清
- JVM 實例:對應一個獨立運行的 Java 程式 (進程級)。
- 執行引擎實例:對應程式內的一條執行緒 (線程級)。
一、JVM 生命週期
誕生
- 啟動
java ClassName args... → OS 載入 JVM 動態庫 → 建立 JVM 實例。
- 入口點:
public static void main(String[]) 的類別名需顯式傳遞。
運行
main() 執行緒為**初始非守護執行緒**。
- JVM 內兩類執行緒:守護 (GC、JIT 編譯) 與 非守護 (main、使用者建立)。
- 使用者可
thread.setDaemon(true) 標記守護執行緒。
消亡
- **所有非守護執行緒結束** → JVM 退出。
- 或呼叫
System.exit() / Runtime.getRuntime().halt() (需 SecurityManager 許可)。
二、JVM 內部架構三大子系統
+---------------------------+
| Class Loader | ← 載入 .class 檔案
+---------------------------+
| Runtime Data Areas | ← 方法區、堆、棧、PC、本地方法棧
+---------------------------+
| Execution Engine | ← 解釋器、JIT、自適應優化
+---------------------------+
三、類載入器子系統
兩大類載入器
| 類型 | 來源 | 搜尋路徑 |
| 啟動類載入器 | JVM 實現 (C++) | 系統類 (rt.jar, $JAVA_HOME/jre/lib) |
| 使用者自定義類載入器 | Java 程式 (繼承 ClassLoader) | CLASSPATH、網路、資料庫、動態生成等 |
關鍵方法 (ClassLoader)
protected final Class defineClass(String name, byte[] data, int off, int len)
protected Class findSystemClass(String name) // 委派給系統載入器
protected final void resolveClass(Class c) // 連接階段:驗證、準備、解析
雙親委派模型 (JDK 1.2+)
- 收到載入請求 → 先委派給父載入器。
- 父載入器找不到 → 子載入器嘗試
findClass()。
- **隔離性**:同一載入器載入的類別可互相見;不同載入器載入的類別**互不見** (命名空間隔離)。
四、執行引擎
指令集設計:基於堆疊 (Stack-based)
- 一條指令 = 1 byte Opcode + 0~N 個 Operand。
- 優勢:平台無關 (少暫存器機器友善)、編譯器中間碼統一、Class 檔緊湊、型別明確利於驗證。
執行技術演進
| 代數 | 技術 | 特點 |
| 第一代 | 純解釋 | 啟動快、執行慢、無優化 |
| 第二代 | JIT (Just-In-Time) 編譯 | 熱點編譯為機器碼、編譯耗時、冷啟動慢 |
| 現代 | 自適應優化 (HotSpot) | **解釋 + 後台編譯 + 激進優化 + 去優化**,動態平衡 |
自適應優化核心流程
- 全部方法起始**解釋執行**,同時收集 Profiling 數據 (呼叫次數、迴圈回邊)。
- 熱點方法觸發閾值 → 後台編譯線程編譯為優化機器碼。
- 執行切換至編譯版本。
- 若假設失效 (如類別載入導致多態點變化) → **去優化** 回退解釋執行。
五、運行時資料區
| 區域 | 共享性 | 核心內容 | 生命週期 |
| 方法區 | 全線程共享 | 類型資訊、常量池、靜態變數、方法字節碼、方法表 | JVM 啟動 ~ 關閉 |
| 堆 | 全線程共享 | 所有物件實例、陣列 | JVM 啟動 ~ 關閉 |
| Java 虛擬機棧 | 線程私有 | 棧幀 (局部變數表、操作數棧、幀資料區) | 線程啟動 ~ 結束 |
| 程式計數器 (PC) | 線程私有 | 當前執行指令位址 (Native 方法為 undefined) | 線程啟動 ~ 結束 |
| 本地方法棧 | 線程私有 | Native 方法呼叫狀態 (C Stack 等) | 線程啟動 ~ 結束 |
六、深度剖析:方法區與物件佈局
方法區儲存內容
- 類完整限定名、直接父類、介面列表、修飾符
- 常量池:**字面常量 + 符號引用** (類/欄位/方法) → **動態連接核心**
- 欄位資訊、方法資訊 (含字節碼、操作數棧深度、局部變數表、異常表)
- 方法表:實例方法直接引用陣列 (含繼承) → **動態分派加速**
堆上物件佈局
+------------------+
| Object Header | ← Mark Word (Hash, GC Age, Lock), Class Pointer
+------------------+
| Instance Fields | ← 含父類欄位,按分配策略排序
+------------------+
| Padding | ← 8 字節對齊填充
+------------------+
- Class Pointer:指向方法區類型資料 → 支援
instanceof、反射、動態綁定。
- 方法表指針:類似 C++ vtable,加速虛方法調用。
陣列物件
- 同維度、同元素型別共用一個
Class (如 int[] → [I, Object[][] → [[Ljava/lang/Object;)
七、棧幀結構與方法調用
Frame = {
Local Variables (含 this, 參數, 局部變數) ← 索引訪問
Operand Stack (運算中繼結果) ← push/pop 訪問
Frame Data (常量池解析、返回地址、異常表指針)
}
- 方法返回/拋出異常 → 幀彈出、記憶體釋放。
invokedynamic (JDK 7+) 支援動態語言調用點緩存。
八、本地方法介面 (JNI)
- Native 方法進入 **本地方法棧** (如 C Stack),脫離 JVM 管控。
- JVM 可透過 JNI 動態擴展功能 (檔案系統、圖形介面、高效能運算)。
- 風險:Native 代碼崩潰 → 整個 JVM 進程終止。
總結
JVM 以**類載入器**動態載入程式碼,**運行時資料區**管理記憶體,**執行引擎**高效執行位元組碼。現代 HotSpot 結合解釋與自適應編譯,實現了啟動速度與執行效能的最佳平衡。理解各區域職責與物件佈局,是診斷記憶體洩漏、效能瓶頸、類載入衝突的基石。