垃圾回收策略
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 区大小是否足够。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。