首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JVM 分代垃圾回收核心原理:為什麼分代、如何分代、觸發條件全解析

     編輯:Java 相關知識

核心假設:弱分代假說

JVM 分代收集基於兩個經驗觀察:

  1. 大多數物件朝生夕死: 短命物件佔絕大多數 (如方法局部變數、臨時計算結果、String 拼接產生的中間對象)。
  2. 熬過多次 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)

  • 觸發條件 (任一滿足即觸發):
    1. 老年代空間不足: 晉升物件無法容納 (含 `PretenureSizeThreshold` 大物件、動態年齡晉升、Survivor 放不下)。
    2. 元空間不足: 類載入導致 Metaspace/permGen 滿。
    3. 顯式調用: `System.gc()` / `Runtime.getRuntime().gc()` (建議 `-XX:+DisableExplicitGC` 禁用)。
    4. 堆擴縮策略變化: `-XX:+UseAdaptiveSizePolicy` 動態調整代大小導致。
    5. CMS/G1/ZGC 並發失敗/提前觸發: `CMSInitiatingOccupancyFraction` 閾值、G1 Mixed GC 回收不及時、ZGC 重定位階段 STW 超時。
    6. RMI 定時觸發: `-Dsun.rmi.dgc.server.gcInterval=3600000` (預設 1 小時) 強制 Full GC (分散式 GC 需求)。可禁用 `-XX:+DisableExplicitGC` 或調大間隔。
  • 範圍: **整個堆 (新生代 + 老年代 + 元空間)**。
  • 特點: 耗時長 (百毫秒至秒級)、STW 明顯、**嚴重影響應用響應**,調優核心目標是**減少頻率與單次耗時**。

常見收集器組合與分代對應 (JDK 8 基線)

組合新生代老年代特點
SerialSerial (複製)Serial (標記-整理)單執行緒、Client 模式預設、小記憶體嵌入式
Parallel ScavengeParallel Scavenge (並行複製)Parallel Old (並行標記-整理)**吞吐量優先**、JDK 8 Server 預設、適合批次/科學計算
ParNew + CMSParNew (並行複製)CMS (並發標記-清除)**低停頓優先**、老年代碎片整理需 Full GC、JDK 9 棄用、JDK 14 移除
G1 (Garbage First)Region-based (若干 Region 組成邏輯新生代/老年代)Region-based (混合回收)**區域化、可預測停頓**、JDK 9+ 預設、大堆 (4G+) 首選
ZGC / ShenandoahRegion-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 這頭「隱形野獸」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved