理解 Java 虛擬機中堆與棧的區別,是掌握物件生命週期、參數傳遞機制、效能調優、排查 OOM 的基礎。本文從記憶體結構、分配策略、存取速度、生命週期、常見誤區五個維度深度對比。
| 維度 | 堆 | 棧 (Java 虛擬機棧) |
|---|---|---|
| 儲存內容 | 物件實例、陣列、字串常量池 (JDK 7+) | 基本型別變數、物件引用 (指標)、方法呼叫棧幀、局部變數表、操作數棧 |
| 共享性 | 全線程共享 | 線程私有 |
| 分配方式 | 動態分配 (new、反射、JNI 等) | 編譯期靜態決定大小 (棧幀)、運行期按序壓棧 |
| 存取速度 | 較慢 (需指標解引用、GC 掃描) | 極快 (CPU 緩存友好、指針運算) |
| 生命週期 | 物件無引用時由 GC 回收 | 方法執行結束自動彈出銷毀 |
| 記憶體大小 | 大 (GB 級,-Xmx 控制) | 小 (MB 級,-Xss 控制,預設 1MB) |
| 異常類型 | OutOfMemoryError: Java heap space | StackOverflowError (遞歸過深/大局部變數) |
| 碎片整理 | 需 GC 壓縮/整理 | 無碎片 (LIFO 分配釋放) |
執行緒 T1 執行緒 T2
┌─────────────────┐ ┌─────────────────┐
│ Stack Frame 1 │ │ Stack Frame 1 │
│ - 區域變數 │ │ - 區域變數 │
│ - 引用 ────────┼───────►│ - 引用 ────────┼──┐
└─────────────────┘ └─────────────────┘ │
┌─────────────────┐ │
│ HEAP │ │
│ ┌───────────┐ │ │
│ │ Object A │◄─┘ │
│ └───────────┘ │
│ ┌───────────┐ │
│ │ Object B │◄────┘
│ └───────────┘
└─────────────────┘
void modifyPrimitive(int x) { x = 10; } // 基本型別:複製值,原值不變
void modifyRef(MyObject obj) { obj.setX(10); } // 引用型別:複製引用,指向同一物件
void modifyRefWrong(MyObject obj) { obj = new MyObject(); } // 修改區域引用,不影響外部
void f(int& x) 真正傳引用JVM 透過逃逸分析判斷物件不逃逸出方法/執行緒,可優化為:
// 啟用逃逸分析 (JDK 8+ 預設開啟)
-XX:+DoEscapeAnalysis
-XX:+EliminateAllocations // 標量替換
-XX:+EliminateLocks // 同步消除
| 誤區 | 事實 |
|---|---|
| 「基本型別都在棧上」 | 基本型別作為物件欄位時在堆上;作為區域變數在棧上 |
| 「引用型別都在堆上」 | 引用變數 (指標) 在棧上,指向的物件在堆上 |
| 「堆只存物件」 | 陣列 (基本型別陣列/引用陣列) 也是物件,在堆上 |
| 「棧大小固定不會 OOM」 | 棧過深遞歸會 StackOverflowError;大量線程總棧記憶體可耗盡 |
| 「新建物件必在堆」 | 逃逸分析後可棧上分配/標量替換 |
-Xms -Xmx 設相同值避免震盪,新生代佔 1/3~1/2-Xss2m,但減少可建立線程數-XX:+UseTLAB (預設開) 線程本地分配緩衝,減少 CAS 競爭