前言
JVM 內存結構是 Java 程序運行的基石,直接決定對象分配效率、GC 行為、OOM 故障排查方向。本文依據《Java 虛擬機規範》系統梳理五大運行時數據區,對比線程私有/共享特性,並關聯 JDK 版本演進變化。
一、五大運行時數據區總覽
| 區域 | 線程隔離性 | 創建/銷毀時機 | 核心職責 | OOM 異常 |
| 程序計數器 | 線程私有 | 線程創建/銷毀 | 字節碼行號指示器,分支/循環/異常/線程切換恢復依賴 | 無(規範規定不會 OOM) |
| Java 虛擬機棧 | 線程私有 | 線程創建/銷毀 | 棧幀:局部變量表、操作數棧、動態鏈接、方法出口 | StackOverflowError / OutOfMemoryError |
| 本地方法棧 | 線程私有 | 線程創建/銷毀 | 服務 Native 方法(C/C++),HotSpot 合併為 Java 棧 | StackOverflowError / OutOfMemoryError |
| Java 堆 | 線程共享 | JVM 啟動/關閉 | 對象實例分配主區域,GC 主戰場,可物理不連續邏輯連續 | OutOfMemoryError: Java heap space |
| 方法區 | 線程共享 | JVM 啟動/關閉 | 類元數據、常量、靜態變量、JIT 編譯代碼 | OutOfMemoryError: Metaspace / PermGen space |
二、重點區域深度解析
1. 程序計數器
- 極小內存空間,唯一規範不會 OOM的區域
- 單線程執行 Java 方法時記錄正在執行的字節碼地址;執行 Native 方法時值為 undefined
- 線程切換時保存/恢復 PC,保證邏輯連續性
2. Java 虛擬機棧
棧幀結構
| 組件 | 說明 |
| 局部變量表 | 基本類型、對象引用、returnAddress,Slot 為單位(32位),long/double 佔 2 Slot,編譯期確定大小 |
| 操作數棧 | 字節碼指令操作數入棧/出棧,最大深度編譯期寫入 Code 屬性 |
| 動態鏈接 | 指向運行時常量池中該方法的引用,支持方法調用動態連接 |
| 方法出口 | 正常返回/異常返回地址,恢復調用者棧幀 |
異常場景
StackOverflowError:遞歸過深、棧幀過大、-Xss 設置過小
OutOfMemoryError:線程過多導致總棧內存耗盡(-Xss * 線程數 > 物理內存)
3. Java 堆
- JVM 啟動時創建(
-Xms 初始、-Xmx 最大)
- 所有線程共享,對象實例幾乎全部在這裡分配(標量替換/逃逸分析優化除外)
- 邏輯連續,物理可不連續;現代 GC 分代:新生代 + 老年代
- TLAB (Thread Local Allocation Buffer):每線程在 Eden 區預分配小塊內存,無鎖指針碰撞分配,極大提升分配吞吐
4. 方法區
| JDK 版本 | 實現 | 位置 | 關鍵參數 |
| JDK 7 及之前 | 永久代 | 堆中連續空間 | -XX:PermSize / -XX:MaxPermSize |
| JDK 8 | 元空間 | 本地內存 | -XX:MetaspaceSize / -XX:MaxMetaspaceSize |
存儲內容
- 類元數據:類名、修飾符、父類、接口、字段、方法、註解等
- 運行時常量池:字面量、符號引用(編譯期生成,運行期可動態添加
String.intern())
- 靜態變量(JDK 7+ 字符串常量/靜態變量已移至堆)
- JIT 編譯代碼緩存
5. 運行時常量池
- 方法區一部分,
Class 文件 constant_pool 表運行時呈現
- 編譯期常量:字面量、符號引用
- 運行期動態性:
String.intern() 可將字符串加入池中
- JDK 7+ 字符串常量池移至堆,受 GC 管理,避免 PermGen OOM
三、JDK 版本演進關鍵變化
| 變化點 | JDK 7 | JDK 8+ |
| 方法區實現 | 永久代 | 元空間 |
| 字符串常量池 | 永久代 | 堆 |
| 靜態變量 | 永久代 | 堆 |
| 類元數據 | 永久代 | 元空間 |
| OOM 異常 | PermGen space | Metaspace |
動機:永久代大小難預估,易 OOM;移至本地內存受限於物理內存,動態擴縮,與類卸載機制配合更靈活。
四、直接內存
- 非 JVM 運行時數據區,但頻繁使用(NIO DirectByteBuffer、Netty、RocketMQ)
Unsafe.allocateMemory / DirectByteBuffer 分配
- 不受堆大小限制,受限於物理內存與
-XX:MaxDirectMemorySize
- GC 間接管理:
DirectByteBuffer 通過 Cleaner (PhantomReference) 觸發釋放
- OOM 異常:
OutOfMemoryError: Direct buffer memory
五、故障排查實戰指南
| 現象 | 可能區域 | 排查工具/命令 |
| StackOverflowError | 虛擬機棧/本地方法棧 | -Xss 增大、檢查遞歸、jstack 分析棧深度 |
| Heap OOM | Java 堆 | jmap -histo、MAT 分析堆轉儲、檢查內存洩漏/堆過小 |
| Metaspace OOM | 方法區 | jcmd VM.metaspace、檢查類加載洩漏、動態代理/框架生成類過多 |
| Direct Memory OOM | 直接內存 | -XX:MaxDirectMemorySize、NIO/Netty 緩衝區釋放檢查 |
六、總結
JVM 五大區域各司其職:
- PC 寄存器:線程執行導航儀
- 棧:方法調用鏈路與局部狀態
- 堆:對象王國,GC 主戰場
- 方法區:類元數據倉庫,元空間時代更彈性
- 常量池:字面量與符號引用的匯聚地
掌握區域職責、參數調優、OOM 特徵,是從「會用 Java」進階到「懂 JVM」的必經之路。建議結合 jstat、jmap、jcmd、Arthas、MAT、JOL 等工具,在真實項目中觀察內存佈局與 GC 行為,內化為直覺。