「堆與棧的區別」是 Java 面試必考、性能調優基石。本文從 JVM 內存結構、分配機制、存儲內容、訪問效率、常見誤區五個維度,系統梳理兩者本質差異。
| 維度 | 堆 | 棧 |
|---|---|---|
| 核心職責 | 對象實例、數組分配區 | 方法執行上下文(棧幀) |
| 線程共享 | 全局共享 | 線程私有 |
| 生命周期 | GC 管理,動態增減 | 隨線程/方法創建銷毀 |
| 分配速度 | 較慢 (TLAB、指針碰撞/空闲列表) | 極快 (指針遞增/遞減) |
| 空間大小 | GB 級 (-Xmx) | MB 級 (-Xss, 默認 1MB) |
| 異常類型 | OutOfMemoryError: Java heap space | StackOverflowError |
int, long, double 等 8 種基本類型值。💡 棧幀結構:局部變量表 | 操作數棧 | 動態鏈接 | 方法返回地址 | 附加信息
new Object()、數組、String 對象等。String s1 = "abc"; // 常量池,編譯期確定,全局唯一
String s2 = "abc"; // 復用常量池對象,s1 == s2 → true
String s3 = new String("abc"); // 堆分配新對象,s1 == s3 → false
String s4 = new String("abc"); // 又一個新對象,s3 == s4 → false
String.intern() 可手動入池(JDK 7+ 池移至堆)。StringBuilder,避免產生大量短命字符串對象壓垮 Young GC。Code 屬性)。-Xss),物理內存按需提交。byte[] buf = new byte[1024*1024])。-Xms 初始、-Xmx 最大,建議設為同值避免震蕩。
// 棧上數組訪問
int[] arr = new int[1000]; // 引用在棧,對象在堆
arr[0] = 1; // 1 指令:iaload/iastore
// 對象字段訪問
Obj obj = new Obj();
obj.field = 1; // 2 指令:aload_0 + putfield (需解析引用)
| 誤區 | 事實 |
|---|---|
| 「對象都在堆」 | 逃逸分析通過的對象可棧上分配;JIT 可標量替換消除對象分配。 |
| 「基本類型都在棧」 | 基本類型作為對象字段時在堆;作為局部變量在棧。 |
| 「棧比堆快,所以多用基本類型」 | 基本類型無多態、無方法、無 null 語義,濫用導致貧血模型。正確選擇:領域建模用對象,熱點計算用基本類型/值類型 (Valhalla 項目未來將引入內聯類)。 |
| 「增大 -Xss 能解決 OOM」 | -Xss 過大會減少可創建線程數 (虛擬內存限制),導致 OutOfMemoryError: unable to create native thread。 |
-XX:+DoEscapeAnalysis (JDK 8+ 默認開啟)。-XX:PretenureSizeThreshold 避免 Eden 區大對象複製。棧是執行邏輯的舞臺,堆是數據狀態的倉庫。理解兩者分工、交互與優化機制,是從「會用 Java」進階到「懂 JVM」的關鍵一步。