垃圾回收策略

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的选择

  1. CMS(性价比只选)
  2. 适用于

    • 老年代对象增长比较缓慢
    • 时间响应敏感但是内存受限
    • 业务逻辑可以优化,减少GC压力:优化对象生命周期的管理业务
  3. 不适用于

    • 老年代频繁GC
    • 需要严格的控制时间
  4. G1(无奈只选,只能通过提升配置)
  5. 适用于:

    • 高并发/大内存(>8GB)
    • 老年代增长快而且不可控制
    • 对延迟敏感的应用(<200ms)
  6. 不适用于:

    • 内存成本受限
    • 小堆内存应用

5.业务代码优化思路

  1. 减少短生命周期对象:

    • 避免频繁的创建和销毁
    • 优化使用对象池或者缓存
  2. 合理设计对象的生命周期

    • 尽量减少对象晋升老年代的频率(通过调整JVM -XX:MaxTenuringThreshold),避免年轻人过早啃老
    • 对频繁访问的老年代对象,考虑设计为可复用或共享。
  3. 设置合理的内存分配

    • 根据应用特点调整 Eden 和 Survivor 区大小(-Xmn 或 -XX:SurvivorRatio)。
  4. 监控和调整JVM参数

    • 合理配置GC参数:例如 CMS 的 -XX:CMSInitiatingOccupancyFraction 或 G1 的 -XX:MaxGCPauseMillis。
    • 定期通过工具(如 JVisualVM、JProfiler)分析 GC 行为。

GC参数调优

  1. 主要参数
参数 说明
-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. 调优步骤
    (1)确定GC的特性
  2. 吞吐量大优先:最大化CPU时间处理任务,允许GC时间可以长一点
  3. 低延迟优先:最小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 日志分析

  1. 如何获取 GC 日志

    • JDK8-:(-XX:PrintGCDetails)
    • JDK9+:(-Xlog:gc*:file=gc.log:time,uptime,level,tags)
  2. 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. 分析重点:

(1)Minor GC 停顿时间

  • 查看年轻代 GC 停顿时间是否过高。
  • Eden区是否频繁扩展,考虑调整 -Xmn 或 -XX:NewRatio。

(2)老年代使用情况

  • 如果频繁触发FullGC,检查老年代的成长速度
  • 对 CMS,检查 -XX:CMSInitiatingOccupancyFraction 是否过高。
  • 对 G1,检查老年代分配的 Region 数量。

(3)对象的晋升

  • 如果对象过早晋升到老年代,调整 -XX:MaxTenuringThreshold。
  • 检查 Survivor 区大小是否足够。

爱跑步的猕猴桃
1 声望0 粉丝