Java 程式運行在 Java 虛擬機(JVM)之上,我們常說的「Java 內存」實際上指的就是 JVM 的運行時內存模型。與 C/C++ 開發者需手動管理內存釋放不同,Java 開發者將內存管理全權委託給虛擬機。但作為專業開發者,深入理解虛擬機如何劃分和管理內存區域,對於排查 OOM 問題、優化 GC 性能以及編寫高效代碼至關重要。
一、 JVM 運行時內存模型宏觀架構
JVM 運行時內存模型主要分為兩大類:線程私有區域與線程共享區域。每啟動一個線程,JVM 就會為其分配私有內存空間;而所有線程共享堆和方法區。這種劃分直接體現了 Java 多線程並發模型的核心設計哲學。

(一)線程私有數據區:生命週期與線程同步
- 程序計數器(PC Register):當前線程執行的字節碼行號指示器。極小的內存空間,若執行 Native 方法則為空。它是唯一一個在 JVM 規範中不會拋出 OutOfMemoryError的區域。
- 虛擬機棧(JVM Stack):Java 方法執行的內存模型。每個方法調用對應一個棧幀(Stack Frame)的入棧與出棧過程。棧幀包含局部變量表、操作數棧、動態連接、方法返回地址及額外附加信息。
- 本地方法棧(Native Method Stack):為 Native 方法服務。HotSpot VM 直接將其與虛擬機棧合二為一。
(二)線程共享數據區:核心戰場
- Java 堆(Heap):JVM 管理的最大內存塊,幾乎所有對象實例和數組都在這裡分配,也是垃圾收集器(GC)的主戰場。現代 JVM 引入了 TLAB(Thread Local Allocation Buffer)優化多線程分配性能,並採用分代收集理論(新生代/老年代)提升回收效率。
- 方法區(Method Area):存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等。JDK 8 之後使用元空間(Metaspace)替換了永久代,使用本地內存。
- 運行時常量池(Runtime Constant Pool):方法區的一部分,存放編譯期生成的字面量和符號引用,並具備動態性(如 String.intern() 可在運行期添加常量)。
提示:業界流行的「堆棧」說法過於粗略。「堆」對應 Java 堆,「棧」僅指虛擬機棧,實際內存模型遠比這複雜。
二、 五大核心區域深度剖析

2.1 程序計數器(PC Register)
線程私有,生命週期與線程一致。記錄正在執行的虛擬機字節碼地址;執行 Native 方法時值為 Undefined。作用:字節碼解釋器通過改變 PC 值來選取下一條指令,實現分支、循環、跳轉、異常處理、線程恢復等基礎功能。
2.2 虛擬機棧與棧幀結構
棧幀是支持方法執行的數據結構,包含五大核心組件:
- 局部變量表:編譯期確定大小,以 Slot 為單位存儲方法參數及局部變量(基本類型、引用、returnAddress)。
- 操作數棧:編譯期確定最大深度,作為字節碼指令的操作區(入棧/出棧),數據類型必須與指令嚴格匹配。
- 動態連接:指向運行時常量池中該方法的引用,支持動態連接(運行期將符號引用轉為直接引用)。
- 方法返回地址:正常退出(返回值傳遞給調用者)或異常退出(拋出異常,無返回值)。
- 額外附加信息:由具體 VM 實現定義(如 JIT 編譯信息)。
異常場景:
StackOverflowError:線程請求棧深度超過 VM 允許最大深度(遞歸過深常見)。
OutOfMemoryError:動態擴展棧時無法申請足夠內存。
2.3 本地方法棧
功能與虛擬機棧類似,但服務於 Native 方法。HotSpot 合併兩者。同樣可拋出 StackOverflowError 和 OutOfMemoryError。
2.4 Java 堆:對象分配與 GC 的核心舞臺

核心特性:
- 分代管理:新生代(Eden、Survivor)與老年代,針對不同存活周期對象採用不同 GC 算法(Minor GC / Major GC / Full GC)。
- TLAB 優化:每個線程在 Eden 區預分配一小塊私有緩衝區,分配對象時優先在 TLAB 中通過指針碰撞快速完成,避免 CAS 競爭。
- 對象佈局:對象頭(Mark Word + Klass Pointer)、實例數據、對齊填充。HotSpot 要求對象起始地址 8 字節對齊。
- 逃逸分析優化:JIT 可將未逃逸對象進行棧上分配、標量替換、同步消除,完全避開堆分配。
異常:OutOfMemoryError: Java heap space。
2.5 方法區與運行時常量池
方法區存儲類元數據、常量、靜態變量、JIT 編譯代碼。JDK 8 移除永久代,改用元空間(Metaspace),直接使用本地內存,受 -XX:MaxMetaspaceSize 限制。
運行時常量池屬於方法區一部分,除存放編譯期常量外,還支持運行期動態添加(String.intern())。包含字面量(文本字符串、final 常量)和符號引用(類/接口全限定名、字段/方法名及描述符)。
異常:方法區可拋 OutOfMemoryError;常量池不單獨拋出 OOM(歸屬方法區)。
三、 總結與實戰建議
- 理解內存劃分是排查
OOM、StackOverflow 的前提。
- 合理設置
-Xms、-Xmx、-Xmn、-XX:MetaspaceSize 等參數,避免頻繁 Full GC。
- 善用
jmap、jstack、jvisualvm、Arthas 等工具分析線上內存狀態。
- 關注逃逸分析、TLAB、壓縮指針等 JVM 自動優化機制,編寫對 GC 友好的代碼。