1. 類加載檢查
- 觸發條件:當遇到
new
指令時,JVM首先檢查該指令的參數(類符號引用)是否已在常量池中。 - 檢查內容:
- 類是否已被加載、解析和初始化。
- 若未加載,則觸發類加載過程(加載 → 驗證 → 準備 → 解析 → 初始化)。
- 目的:確保類元數據可用,避免后續操作因類未定義而失敗。
2. 內存分配
- 分配方式:
- 指針碰撞(Bump the Pointer):適用于內存規整的堆(如Serial、ParNew收集器)。
通過移動指針劃分內存,分配速度快(僅需指針移動)。 - 空閑列表(Free List):適用于內存不規整的堆(如CMS收集器)。
維護可用內存塊列表,分配時搜索足夠大的空間。
- 指針碰撞(Bump the Pointer):適用于內存規整的堆(如Serial、ParNew收集器)。
- 線程安全:
- TLAB(Thread Local Allocation Buffer):為每個線程預分配堆內存區域,避免CAS競爭。
- CAS重試:當TLAB不足時,使用CAS同步分配。
3. 初始化零值
- 操作內容:將對象的內存空間初始化為零值。
- 基本類型字段:
int
→0
,boolean
→false
。 - 引用類型字段:
null
。
- 基本類型字段:
- 目的:確保對象字段在不顯式初始化時也能直接使用。
4. 設置對象頭(Object Header)
- 對象頭結構(64位JVM):
- Mark Word:存儲哈希碼、GC分代年齡、鎖狀態(偏向鎖/輕量級鎖/重量級鎖)等信息。
- Klass Pointer:指向類元數據,確定對象類型。
- 示例:
|---------------------------| | Mark Word (64位) | |---------------------------| | Klass Pointer (32位) | |---------------------------|
5. 執行 <init>
方法
- 步驟:
- 初始化父類:遞歸調用父類構造方法(
super()
)。 - 實例變量賦值:按代碼順序執行顯式初始化和構造代碼塊。
- 構造器代碼:執行用戶編寫的構造方法邏輯。
- 初始化父類:遞歸調用父類構造方法(
- 目的:完成對象按業務需求的初始化。
內存分配優化策略
策略 | 說明 | 適用場景 |
---|---|---|
TLAB分配 | 線程私有內存區域,減少CAS競爭 | 高頻創建小對象的場景 |
逃逸分析 | 若對象未逃逸方法,可能在棧上分配或標量替換 | 方法內部臨時對象(JIT優化) |
大對象直接進入老年代 | 避免在新生代頻繁復制(通過 -XX:PretenureSizeThreshold 設置閾值) | 大數組、大字符串等 |
對象內存布局
區域 | 內容 | 大小(64位JVM) |
---|---|---|
對象頭(Header) | Mark Word(鎖狀態、哈希碼等) + Klass Pointer(類元數據指針) | 12字節(開啟壓縮指針) |
實例數據(Instance Data) | 對象實際字段值(包括父類繼承字段) | 由字段類型和數量決定 |
對齊填充(Padding) | 補齊對象大小為8字節的整數倍 | 0~7字節 |
常見問題與解決方案
-
內存分配失敗:
- 觸發GC:當Eden區空間不足時,觸發Minor GC。
- OOM處理:若GC后仍無法分配,拋出
OutOfMemoryError
。
-
線程競爭:
- TLAB優化:通過
-XX:+UseTLAB
啟用(默認開啟),減少CAS沖突。
- TLAB優化:通過
-
對象初始化順序:
- 字段默認值 → 顯式賦值 → 構造器代碼:確保初始化符合Java規范。
總結
Hotspot虛擬機通過 類加載檢查 → 內存分配 → 初始化 → 對象頭設置 → 構造方法調用 的流程創建對象,結合 TLAB、逃逸分析 等優化策略,平衡性能與安全性。理解這一過程有助于優化代碼(如減少大對象創建)和排查內存問題(如OOM)。