JVM 運行時數據區是 Java 程序執行的內存模型基石。本文通過一個具體代碼示例,全流程追蹤 AppMain 啟動、Sample 對象創建、方法調用的完整過程,可視化展示堆、棧、方法區三大區域的數據分佈、引用關係與生命週期。
| 區域 | 別名 | 線程共享性 | 核心存儲內容 | 異常類型 |
|---|---|---|---|---|
| 堆 | Heap | 所有線程共享 | 對象實例、數組、字符串常量池 (JDK 7+)、靜態變量引用對象 | OOM |
| 棧 | JVM Stack | 線程私有 | 棧幀:局部變量表(基本類型/對象引用)、操作數棧、動態連接、方法返回地址 | StackOverflowError / OOM |
| 方法區 | Metaspace (JDK 8+) | 所有線程共享 | 類元數據、常量、靜態變量、JIT 編譯代碼、符號引用 | OOM (Metaspace) |
// AppMain.java
public class AppMain {
public static void main(String[] args) {
Sample test1 = new Sample("測試1"); // 引用變量 test1 在棧,對象在堆
Sample test2 = new Sample("測試2"); // 引用變量 test2 在棧,對象在堆
test1.printName(); // 調用實例方法
test2.printName();
}
}
// Sample.java
public class Sample {
private String name; // 實例字段:引用在堆對象內部,指向字符串對象
public Sample(String name) {
this.name = name; // 賦值:name 引用指向參數字符串對象
}
public void printName() {
System.out.println(name); // 讀取實例字段,調用 println
}
}
AppMain.class 字節碼AppMain 的 Klass 元數據結構:
main 方法描述符main 字節碼指令、異常表、行號表AppMain 無靜態字段Sample 類,方法區建立 Sample 的 Klass 元數據Thread: main ┌─────────────────────────────────────┐ │ Stack Frame: AppMain.main() │ ├─────────────────────────────────────┤ │ Local Variables: │ │ [0] args:String[] = @ref_0x100 │ ← 引用指向堆中 String[] 對象 │ [1] test1:Sample = null │ ← 待賦值 │ [2] test2:Sample = null │ ← 待賦值 │ Operand Stack: (empty) │ │ Dynamic Linking: → AppMain Klass │ │ Return Address: (native) │ └─────────────────────────────────────┘
new Sample("測試1") —— 對象出生三部曲Sample 已加載(方法區有 Klass)name (reference 類型,4/8 字節)<init> 構造器字節碼
"測試1" 為字符串常量,已在堆/常量池this.name = "測試1":將引用寫入對象 name 字段槽位Local Variables 更新: [1] test1:Sample = @ref_0x200 ← 指向堆中新創建的 Sample 對象 (地址 0x200)
Heap 新增對象 @ref_0x300 (Sample "測試2") Local Variables: [2] test2:Sample = @ref_0x300
test1.printName() —— 方法調用棧幀test1 → 堆 @ref_0x200Sample KlassprintName 方法字節碼Thread: main ┌─────────────────────────────────────┐ ← 新棧幀 (頂部) │ Stack Frame: Sample.printName() │ ├─────────────────────────────────────┤ │ Local Variables: │ │ [0] this:Sample = @ref_0x200 │ ← 隱式 this 引用 │ Operand Stack: (empty) │ │ Dynamic Linking: → Sample Klass │ │ Return Address: → main() 下一條指令 │ └─────────────────────────────────────┘ ┌─────────────────────────────────────┐ ← 原棧幀 │ Stack Frame: AppMain.main() │ ├─────────────────────────────────────┤ │ Local Variables: │ │ [0] args = @ref_0x100 │ │ [1] test1 = @ref_0x200 │ │ [2] test2 = @ref_0x300 │ └─────────────────────────────────────┘
System.out.println(name)this 引用 (@ref_0x200) 讀取 name 字段 → 字符串對象引用PrintStream.println(String) → 新棧幀 (JDK 內部類)┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ Thread Stack │ │ Heap │ │ Metaspace │ │ (線程私有) │ │ (線程共享) │ │ (線程共享) │ ├──────────────────┤ ├──────────────────┤ ├──────────────────┤ │ Frame: main() │ │ @0x200 Sample │ │ Klass: AppMain │ │ test1 ──────────┼────▶│ name → @str1 │ │ main() bytecode │ │ test2 ──────────┼────▶│ @0x300 Sample │ │ Klass: Sample │ │ args ──────────┼────▶│ name → @str2 │ │() │ │ │ │ @str1 "測試1" │ │ printName() │ │ │ │ @str2 "測試2" │ │ name field │ │ │ │ @0x100 String[] │ │ │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ │ │ │ 引用 (4/8 bytes) │ 對象實例 │ 類元數據 │ │ Klass Pointer ───────▶│ └────────────────────────┴────────────────────────┘
| 概念 | 存儲位置 | 說明 |
|---|---|---|
| 對象引用變量 | 棧 (局部變量表) / 堆 (實例字段) | 4 字節 (32位) 或 8 字節 (64位 + UseCompressedOops) |
| 對象實例 | 堆 | 包含對象頭、實例數據、對齊填充 |
| 類元數據 | 方法區 | Klass 結構,含方法字節碼、常量池、字段表 |
| 基本類型變量 | 棧 (局部變量) / 堆 (實例字段) | 直接存值,非引用 |
| 字符串常量 | 堆 (JDK 7+) / 永久代 (JDK 6) | "測試1" 編譯期進入常量池,運行期在堆 |
| 方法字節碼 | 方法區 | JIT 編譯後的機器碼緩存在 Code Cache |
將代碼執行映射到三區交互模型,是理解 JVM 內存管理、GC 行為、性能調優的核心思維模型。