執行引擎是 Java 虛擬機最核心的組件之一。物理機的執行引擎建立在處理器、硬件指令集與作業系統層面,而 JVM 執行引擎則由自定義的字節碼指令集與執行引擎架構構成。理解其工作原理,有助於我們掌握 Java 程式的實際運行機制、性能優化方向以及 JVM 參數調優的本質。
一、 執行引擎架構與執行模式
JVM 執行引擎在執行 Java 代碼時,擁有兩種主要執行模式:
- 解釋執行:通過解釋器逐條讀取字節碼,解析並執行。啟動快、佔用內存少,但執行效率較低。
- 編譯執行:通過即時編譯器(JIT, Just-In-Time Compiler)將熱點字節碼編譯為本地機器碼直接執行。啟動慢、佔用內存多(代碼緩存),但執行效率極高,可進行深度優化(內聯、逃逸分析等)。
補充:Android ART 引入了 AOT(Ahead-Of-Time)提前編譯,在安裝階段將字節碼編譯為本地碼,結合了兩者優勢。
現代商用 JVM(HotSpot)採用混合模式:默認開啟分層編譯,代碼先由解釋器執行,隨著熱度上升逐步觸發 C1(Client Compiler)和 C2(Server Compiler)分層編譯,實現啟動速度與峰值性能的平衡。
二、 解釋器執行模型:字節碼指令的處理循環
忽略異常處理,解釋器的核心執行模型可抽象為如下偽代碼循環:
do {
atomically calculate pc and fetch opcode at pc;
if (operands) fetch operands;
execute the action for the opcode;
} while (there is more to do);
- 計算 PC 並取指令:原子性獲取程序計數器指向的操作碼。
- 取操作數:若指令需要操作數,從字節碼流中讀取(可能多字節,大端序)。
- 執行動作:根據操作碼調用對應的處理邏輯(修改棧幀、操作數棧、局部變量表、堆內存等)。
- 循環:直到方法返回或異常拋出。
三、 對象創建全流程:從 new 指令到構造函數執行完畢
以 Person person = new Person() 為例,虛擬機遇到 new 指令時經歷以下步驟:
- 類加載檢查:在常量池定位「Person」符號引用,檢查類是否已加載、解析、初始化。未初始化則優先觸發類加載流程(雙親委派、驗證、準備、解析、初始化)。
- 內存分配:類加載完成後,對象大小已確定。在堆中分配內存,主流方式:
- 指針碰撞:內存規整(Serial、ParNew GC),移動指針即可。
- 空閒列表:內存不規整(CMS、G1),維護列表查找合適塊。
並發分配安全性:CAS + 失敗重試 或 TLAB(Thread Local Allocation Buffer) 線程私有緩衝區優先分配。
- 內存初始化(零值填充):分配的內存(除對象頭外)初始化為零值,保證實例字段無賦值即可使用默認值。
- 設置對象頭:填充 Mark Word(哈希碼、GC 分代年齡、鎖狀態、偏向鎖 ID 等)與 Klass Pointer(指向類元數據)。
- 執行 <init> 構造函數:從 JVM 視角對象已創建完成;但 Java 代碼視角,字段仍為零值。執行構造函數按程式員意圖初始化字段,至此對象真正可用。
四、 關鍵優化技術點
- 逃逸分析:JIT 編譯階段分析對象作用域,未逃逸對象可進行棧上分配(無需 GC)、標量替換(拆解為局部變量)、同步消除(移除不必要的鎖)。
- 壓縮指針:
-XX:+UseCompressedOops(64 位 JVM 默認開啟),將 64 位引用壓縮為 32 位偏移量,節省 30%~50% 堆內存。
- 分層編譯:
-XX:+TieredCompilation(默認開啟),C1 快速編譯收集 Profile,C2 基於 Profile 激進優化。
- 去虛擬化:JIT 根據 Profile 將虛方法調用內聯為直接調用,必要時保留守護條件。
五、 總結
JVM 執行引擎通過解釋器快速啟動、JIT 編譯器極致優化、混合模式平衡兩者,構建了 Java「一次編譯、到處運行」且高性能的基石。深入理解字節碼執行循環、對象創建流程、JIT 優化手段,是進階 JVM 調優與高性能 Java 開發的必修課。