核心假設:弱分代假說
JVM 分代收集基於兩個經驗觀察:
- 大多數物件朝生夕死: 短命物件佔絕大多數 (如方法局部變數、臨時計算結果、String 拼接產生的中間對象)。
- 熬過多次 GC 的物件傾向長期存活: 長命物件 (單例、緩存、線程池、Session、連接池) 存活率極高。
**結論**:針對不同壽命物件採用不同演算法,**分治優於整體掃描**,大幅提升吞吐量與降低停頓。
堆記憶體分代佈局 (HotSpot 經典架構)
+--------------------------+
| Permanent Gen | (JDK 8 以前) / Metaspace (JDK 8+) - 類元數據、常量池、靜態變數
+--------------------------+
| Old Generation | 長命物件、大物件、熬過多次 YGC 的物件
| (Tenured Generation) |
+--------------------------+
| Young Generation | 新生物件誕生地
| +------+------+------+ |
| | Eden | S0 | S1 | | S0/S1 為 Survivor 空間 (From/To 角色動態互換)
| +------+------+------+ |
+--------------------------+
新生代詳解
- Eden 區: 絕大多數物件分配於此 (TLAB 線程本地分配緩衝優化)。
- Survivor 空間 (S0/S1): 兩塊大小相等、角色對稱的空間。每次 Young GC (YGC/Minor GC) 後,存活物件從 Eden + From Survivor 複製到 To Survivor。**永遠有一塊 Survivor 為空**。
- 動態年齡判定: 物件在 Survivor 每熬過一次 YGC,年齡 +1。達到 `MaxTenuringThreshold` (預設 15, CMS 為 6) 或 Survivor 空間佔用超過 `TargetSurvivorRatio` (預設 50%) 時,**提前晉升老年代**。
- 大物件直接進老年代: `-XX:PretenureSizeThreshold` (預設 0 關閉) 設定閾值,避免 Eden/Survivor 間大量複製開銷。
老年代詳解
- 接收晉升物件、大物件、長命物件。
- 回收頻率低、單次耗時長 (Full GC / Major GC)。
- 演算法:標記-清除-整理 或 標記-整理 (CMS 並發標記-清除、G1/ZGC 混合模式)。
永久代 / 元空間
- PermGen (JDK 7-): 固定大小 (`-XX:MaxPermSize`),存類元數據、常量池、Interned String。**易 OOM**,需調優。
- Metaspace (JDK 8+): **本地記憶體 (Native Memory)**,預設無上限 (受限於物理記憶體),可設 `-XX:MaxMetaspaceSize`。類卸載觸發條件更嚴格。
兩種 GC 模式與觸發條件
1. Young GC (Minor GC / Scavenge GC)
- 觸發: **Eden 區分配失敗** (空間不足)。
- 範圍: 僅掃描新生代 (Eden + Survivor)。
- 特點: 頻繁 (毫秒級)、速度快 (複製演算法、只掃描存活物件)、Stop-the-World (STW) 時間短。
- 後續: 存活物件 → Survivor / 老年代。
2. Full GC (Major GC)
- 觸發條件 (任一滿足即觸發):
- 老年代空間不足: 晉升物件無法容納 (含 `PretenureSizeThreshold` 大物件、動態年齡晉升、Survivor 放不下)。
- 元空間不足: 類載入導致 Metaspace/permGen 滿。
- 顯式調用: `System.gc()` / `Runtime.getRuntime().gc()` (建議 `-XX:+DisableExplicitGC` 禁用)。
- 堆擴縮策略變化: `-XX:+UseAdaptiveSizePolicy` 動態調整代大小導致。
- CMS/G1/ZGC 並發失敗/提前觸發: `CMSInitiatingOccupancyFraction` 閾值、G1 Mixed GC 回收不及時、ZGC 重定位階段 STW 超時。
- RMI 定時觸發: `-Dsun.rmi.dgc.server.gcInterval=3600000` (預設 1 小時) 強制 Full GC (分散式 GC 需求)。可禁用 `-XX:+DisableExplicitGC` 或調大間隔。
- 範圍: **整個堆 (新生代 + 老年代 + 元空間)**。
- 特點: 耗時長 (百毫秒至秒級)、STW 明顯、**嚴重影響應用響應**,調優核心目標是**減少頻率與單次耗時**。
常見收集器組合與分代對應 (JDK 8 基線)
| 組合 | 新生代 | 老年代 | 特點 |
| Serial | Serial (複製) | Serial (標記-整理) | 單執行緒、Client 模式預設、小記憶體嵌入式 |
| Parallel Scavenge | Parallel Scavenge (並行複製) | Parallel Old (並行標記-整理) | **吞吐量優先**、JDK 8 Server 預設、適合批次/科學計算 |
| ParNew + CMS | ParNew (並行複製) | CMS (並發標記-清除) | **低停頓優先**、老年代碎片整理需 Full GC、JDK 9 棄用、JDK 14 移除 |
| G1 (Garbage First) | Region-based (若干 Region 組成邏輯新生代/老年代) | Region-based (混合回收) | **區域化、可預測停頓**、JDK 9+ 預設、大堆 (4G+) 首選 |
| ZGC / Shenandoah | Region-based (染色指標/連接矩陣) | Region-based (並發重定位) | **超低停頓 (<10ms)**、JDK 11/12+、超大堆 (TB 級)、生產環境成熟度提升中 |
調優核心觀測指標
- YGC 頻率 / 耗時 / 存活率: 過高頻率 → 增大新生代 (`-Xmn` / `-XX:NewRatio`);存活率高 → 檢查物件洩漏或調大 Survivor。
- Full GC 頻率 / 耗時 / 老年代佔用趨勢: 核心關注點。頻繁 Full GC → 擴大老年代、調整晋升閾值、檢查大物件/類洩漏、切換低停頓收集器 (G1/ZGC)。
- 元空間增長曲線: 持續增長不穩 → 類載入洩漏 (動態代理、Groovy、JSP 重新編譯、OSGi 熱部署) → 設 `-XX:MaxMetaspaceSize` 兜底。
- 分配速率 (Allocation Rate): MB/sec。過高 → 壓力大 → 需更大新生代或更快收集器。
理解分代假說與堆佈局,是 JVM GC 調優的**認知基石**。實戰中,配合 `-Xlog:gc*:file=gc.log:time,uptime,level,tags` (JDK 9+) 或 `-XX:+PrintGCDetails -Xloggc:gc.log` (JDK 8) 產出日誌,使用 GCeasy / GCEasy / Arthas / JVisualVM / JMC 分析,對症下藥,方能馴服 GC 這頭「隱形野獸」。