垃圾回收策略
1. JVM 垃圾回收策略機制描述
垃圾回收(GC)是JVM自動管理內存的一部分,用於釋放不再使用的對象所佔的內存空間。它的目標是:
(1)提升內存的使用率
(2)減少開發者手動管理內存的複雜性
2. 垃圾回收的主要策略
JVM的GC策略圍繞分代收集理論展開,任務不同時期的對象適合不同方式的回收
(1)分代收集:
- 新生代垃圾回收採用 Minor GC(輕量級且頻繁)。
- 老年代回收採用 Major GC 或 Full GC(耗時更長)。
- 元空間:存儲類的元數據,取代了 JDK 8 之前的方法區。
(2)GC算法
- 標記清理算法:缺點(產生大量碎片)
- 複製算法:優點(適用於需要大量清理的空間,如新生代)
- 標記整理算法:優點(減少內存碎片,適合老年代)
- 分代收集算法:針對以上算法,針對不同代的特點進行優化
3. G1垃圾回收器原理
G1(Garbage First) 是 Java 7 引入的一種面向服務器的垃圾收集器,旨在提供低停頓、高吞吐的 GC 體驗。
(1)分區管理:
- 將堆劃分為多個大小相等的域,每一個域可以是Eden,Survivor或者老年代
- 動態分配域的角色,提升利用率
(2)並行和併發
- GC工作線程可以併發執行
- 一些耗時的操作(標記對象)在用户線程運行的時候併發執行。
(3)預測停頓時間
- 允許用户設置最大停頓時間,並且儘量滿足
(4)增量刪除
- 將回收任務分成小塊,以避免長時間的 Full GC 停頓
工作流程:
(1)初始標記階段
- 標記於GC ROOT直接相關的對象
- 時間短,與應用線程並行
(2)併發標記階段
- 遍歷對象圖
- 不會阻塞應用線程
(3)最終標記階段
- 處理併發標記階段遺留的對象,重新標記
- 短暫暫停
(4)篩選回收階段
- 看那個區域(Region)的垃圾比較多,回收垃圾最多的區域
- 執行標記整理,回收內存並減少碎片
適用場景:
大內存應用(6G以上)
對低延遲有強烈要求的應用
4.CMS與G1的選擇
- CMS(性價比只選)
-
適用於
- 老年代對象增長比較緩慢
- 時間響應敏感但是內存受限
- 業務邏輯可以優化,減少GC壓力:優化對象生命週期的管理業務
-
不適用於
- 老年代頻繁GC
- 需要嚴格的控制時間
- G1(無奈只選,只能通過提升配置)
-
適用於:
- 高併發/大內存(>8GB)
- 老年代增長快而且不可控制
- 對延遲敏感的應用(<200ms)
-
不適用於:
- 內存成本受限
- 小堆內存應用
5.業務代碼優化思路
-
減少短生命週期對象:
- 避免頻繁的創建和銷燬
- 優化使用對象池或者緩存
-
合理設計對象的生命週期
- 儘量減少對象晉升老年代的頻率(通過調整JVM -XX:MaxTenuringThreshold),避免年輕人過早啃老
- 對頻繁訪問的老年代對象,考慮設計為可複用或共享。
-
設置合理的內存分配
- 根據應用特點調整 Eden 和 Survivor 區大小(-Xmn 或 -XX:SurvivorRatio)。
-
監控和調整JVM參數
- 合理配置GC參數:例如 CMS 的 -XX:CMSInitiatingOccupancyFraction 或 G1 的 -XX:MaxGCPauseMillis。
- 定期通過工具(如 JVisualVM、JProfiler)分析 GC 行為。
GC參數調優
- 主要參數
| 參數 | 説明 |
|---|---|
| -XX:+UseG1GC | 使用 G1 垃圾收集器 |
| --XX:+UseConcMarkSweepGC | 使用 CMS 垃圾收集器 |
| -XX:NewRatio=ratio | 新生代和老年代的比例,如2表示老年代是新生代的2倍 |
| -Xmnsize | 設置新生代的大小(絕對值) |
| -XX:SurvivorRatio=ratio | 設置 Eden 區與 Survivor 區的大小比,例如 8 表示 Eden:Survivor = 8:1 |
| -XX:MaxGCPauseMillis=ms | (G1 特有)設置期望的 GC 最大停頓時間(毫秒) |
| -XX:CMSInitiatingOccupancyFraction=n | (CMS 特有)設置 CMS 在老年代使用率達到 n% 時開始執行回收 |
| -XX:+PrintGCDetails | 輸出詳細 GC 日誌 |
| -Xlog:gc* | (JDK 9+)打印 GC 日誌,支持更靈活的格式,如 -Xlog:gc*=info:file=gc.log:uptime,level |
- 調優步驟
(1)確定GC的特性 - 吞吐量大優先:最大化CPU時間處理任務,允許GC時間可以長一點
- 低延遲優先:最小GC停頓時間, 最小化 GC 停頓時間,可能犧牲一定的吞吐量
(2) 選擇合適的GC
- 小內存(<6G):優先使用 Parallel GC 或 CMS。
- 大內存(>=6G):優點使用G1
- 延遲敏感且大內存:選擇 G1 或 ZGC
(3) 對大小的劃分與比例
- 堆大小(-Xms 和 -Xmx):根據系統內存和應用需求設置堆的初始大小和最大大小,一般兩者設為相同值,避免擴展造成性能損耗。
- 新生代大小(-Xmn 或 -XX:NewRatio):新生代適合大部分短生命週期對象,分配更多空間可以減少 Minor GC。
- Eden 與 Survivor 比例(-XX:SurvivorRatio):根據對象存活率,默認8適合大部份場景
(4) 調整回收參數
-
CMS 調優:
- 提前啓動回收:-XX:CMSInitiatingOccupancyFraction=75(75% 老年代佔用時啓動 GC)。
- 避免併發失敗:-XX:+UseCMSInitiatingOccupancyOnly 保證固定觸發條件。
-
G1 調優
- 應用最大停頓時間:-XX:MaxGCPauseMillis=200 控制應用的最大停頓時間。
- 回收目標區域大小:-XX:G1HeapRegionSize=16M 調整單個 Region 的大小,減少或增加 Region 數量。
(5) 持續監控和調整
- 使用 APM 工具(如 Prometheus + Grafana 或 JVisualVM)監控 GC 性能。
- 根據 GC 日誌和實際停頓時間不斷優化。
GC 日誌分析
-
如何獲取 GC 日誌
- JDK8-:(-XX:PrintGCDetails)
- JDK9+:(-Xlog:gc*:file=gc.log:time,uptime,level,tags)
- GC 日誌內容解析
(1) CMS日誌
2024-12-07T10:00:00.000+0000: [GC (Allocation Failure) [ParNew: 2048K->512K(6144K), 0.0056789 secs]
[CMS: 10240K->8192K(20480K), 0.0123456 secs] 12288K->8704K(26624K), [Metaspace: 1024K->1024K(1024K)]]
[GC (Allocation Failure)]:GC 觸發原因(內存分配失敗)。- ParNew:新生代回收,Eden 區從 2048KB 回收至 512KB,耗時 0.0056 秒。
- CMS:老年代回收,從 10240KB 減少到 8192KB,耗時 0.012 秒。
- 總堆使用情況:從 12288KB 降至 8704KB。
(2) G1日誌
[2024-12-07T10:00:00.000+0000][info][gc,start ] GC Pause (G1 Evacuation Pause) (young) 50M->30M(100M)
[2024-12-07T10:00:00.050+0000][info][gc,heap ] Eden regions: 4->0(6)
[2024-12-07T10:00:00.050+0000][info][gc,heap ] Survivor regions: 2->2(3)
[2024-12-07T10:00:00.050+0000][info][gc,heap ] Old regions: 20->20
[2024-12-07T10:00:00.050+0000][info][gc,end ] GC Pause (G1 Evacuation Pause) (young) 50M->30M(100M) 50ms
- GC 類型:G1 Evacuation Pause (young) 表示年輕代回收。
- 堆變化:堆從 50MB 降至 30MB,總堆大小為 100MB。
-
區域變化:
- Eden 區從 4 個區域回收為 0。
- Survivor 區保持不變(2 個區域)。
- 老年代區域未受影響。
- 分析重點:
(1)Minor GC 停頓時間
- 查看年輕代 GC 停頓時間是否過高。
- Eden區是否頻繁擴展,考慮調整 -Xmn 或 -XX:NewRatio。
(2)老年代使用情況
- 如果頻繁觸發FullGC,檢查老年代的成長速度
- 對 CMS,檢查 -XX:CMSInitiatingOccupancyFraction 是否過高。
- 對 G1,檢查老年代分配的 Region 數量。
(3)對象的晉升
- 如果對象過早晉升到老年代,調整 -XX:MaxTenuringThreshold。
- 檢查 Survivor 區大小是否足夠。