核心架構:棧與堆的分工
Java 程序運行在 JVM 虛擬出的內存空間中,核心劃分為棧與堆兩大區域,職責截然不同。
棧:線程私有,方法調用的賬本
- 每條線程擁有獨立的棧,生命週期與線程同步。
- 記錄方法調用鏈:每次調用壓入一個幀,包含局部變量(基本類型、對象引用)、操作數棧、動態鏈接、返回地址。
- 特點:分配釋放極快(指針移動),無 GC 涉及,但容量有限(-Xss 可調),深度遞歸易導致
StackOverflowError。
- 關鍵規則:棧中絕不存放對象實體,僅存基本類型值與對象引用(指向堆中對象)。
堆:全局共享,對象實體的倉庫
- 所有線程共享,JVM 啟動時創建(-Xms/-Xmx 控制大小)。
- 存放幾乎所有對象實例與數組。
- 不隨方法結束自動釋放,必須依賴垃圾回收器 (GC) 回收無用對象。
垃圾回收核心原理:可達性分析
早期引用計數法無法解決循環引用問題。現代 JVM 採用可達性分析:以 GC Roots (棧中引用、靜態字段、常量、JNI 引用等) 為起點,向下搜索引用鏈。可達對象 = 存活對象;不可達對象 = 垃圾,待回收。
經典 GC 算法與分代策略
兩大基礎算法
| 算法 | 核心邏輯 | 優點 | 缺點 | 適用場景 |
| 標記-清除 | 標記存活,掃描清理不可達 | 實現簡單 | 產生內存碎片,分配大對象困難 | 對象存活率高(老年代) |
| 複製-清理 | 劃分兩塊區域,存活對象複製到空閒區並緊湊排列 | 無碎片,分配極快(指針碰撞) | 浪費 50% 空間 | 對象朝生夕死(新生代) |
分代收集:融合兩者優勢
基於弱分代假設:絕大多數對象朝生夕死,熬過多次 GC 的對象極難死。JVM 將堆劃分為三代:
- 新生代:採用複製算法。再細分為 Eden + 兩塊大小相等的 Survivor (From/To)。
- 新對象優先在 Eden 分配。
- Eden 滿觸發 Minor GC:存活對象複製到 To 區,Eden、From 清空,角色互換。
- 大對象/長壽對象直接晉升老年代(-XX:PretenureSizeThreshold, -XX:MaxTenuringThreshold)。
- 老年代:採用標記-清除/整理。空間不足觸發 Major GC / Full GC,停頓時間長。
- 永久代/元空間:存放類元數據。Java 8 移除永久代,改用本地內存元空間,默認無上限(-XX:MaxMetaspaceSize 可限制)。
總結
JVM 內存管理是動態平衡的藝術:棧負責高效執行,堆承載數據生命週期,分代 GC 以空間換時間、以局部收集換低延遲。理解這套機制,是排查 OOM、優化停頓、調優 -Xms/Xmx/NewRatio/SurvivorRatio 等參數的基石。具體 GC 實現(G1, ZGC, Shenandoah)隨版本演進,但分代思想與可達性分析始終是核心不變量。