登录
首页 >  文章 >  java教程

G1 GC 暂停飙升排查:别先复制 JVM 参数,先看 JFR 和 GC 日志

来源:17golang Java频道原创

时间:2026-06-04 14:11:24 327浏览 收藏

这篇写一个 Java 线上性能排查里最容易被“玄学参数”带偏的问题:接口 p99 突然出现尖刺,GC 日志里 G1 Pause 从几十毫秒变成两三百毫秒。很多人第一反应是去网上复制一串 JVM 参数,但真正有效的顺序应该是先拿证据,再小步调优。

本文适用于 Java 17/21、Spring Boot 服务和默认使用 G1 GC 的常见后端应用。资料只用于核对事实:G1 的暂停目标是软目标,官方也建议先从默认设置、最大堆和 GC 日志/JFR 证据出发。正文按生产复盘写,不搬调参清单。

G1 GC 暂停排查思维导图
脑图:GC 排查从现象、证据、根因、修复和上线闭环展开。

业务场景:支付回调 p99 每隔几分钟尖一下

支付回调服务平时延迟很稳,某次活动后 p99 每隔几分钟就抬到 800ms。CPU 没满,数据库也没慢,但 GC 日志显示年轻代暂停明显变长,JFR 里还能看到大对象分配增多。

这类问题最怕只看平均值。平均耗时可能还行,但用户感受到的是尖刺。我们要把业务 p99、GC pause、堆占用、分配速率放到同一条时间线上看。

问题复现:批量查询加 JSON 序列化放大分配

最小复现是一个批量接口:一次查 2000 条订单,再把整批对象组装成大 JSON 返回。低峰没问题,高峰时 Eden 很快被填满,大对象还可能进入 humongous region,G1 为了回收和整理会产生更明显的暂停。

参数不是没有用,但如果业务代码持续制造大量短命对象和大对象,单靠 MaxGCPauseMillis 很难从根上解决问题。

G1 GC 暂停诊断流程
流程图:先对齐现象,再用 GC 日志和 JFR 找分配源。

踩坑原因:把暂停目标当硬指标

-XX:MaxGCPauseMillis 是目标,不是保证。目标设得太激进,G1 会尝试缩短暂停,但吞吐、并发回收压力和堆空间都会受影响。业务分配模式不变,暂停可能换一种方式继续出现。

我见过最危险的做法,是把一堆看不懂的 G1 参数复制到生产:新生代比例、IHOP、保留百分比、并发线程全改。短期似乎好了,下一次流量形态变化又出新问题,而且没人知道哪个参数起了作用。

代码案例:先打开可观测,再谈调参

下面这张图不是说参数永远不能调,而是建议先保留证据。没有 GC 日志、没有 JFR、没有分配来源,调参就是盲飞。

G1 GC JVM 参数对比
先观测,再少量调优;每次只改少数参数,并做灰度对比。
JAVA_OPTS="
  -Xms4g -Xmx4g
  -Xlog:gc*,safepoint:file=/logs/gc-%t.log:time,level,tags:filecount=10,filesize=50M
  -XX:MaxGCPauseMillis=200
  -XX:+HeapDumpOnOutOfMemoryError
  -XX:HeapDumpPath=/logs/heapdump.hprof
"

如果 JFR 显示分配集中在某个 JSON 转换、批量列表、缓存复制或日志拼接,优先改代码:分页、流式处理、减少中间对象、复用缓冲、限制响应大小。这些通常比把暂停目标从 200ms 改成 50ms 更靠谱。

诊断步骤:我会按这六步走

第一步,对齐时间线。 把接口 p99、GC pause、CPU、堆占用、分配速率放在同一张图,不要只看单点日志。

第二步,打开 GC 日志。 看 young pause、mixed GC、humongous allocation、to-space exhausted、remark/cleanup 等关键信息。

第三步,抓 JFR。 重点看 GC Pause、Allocation in new TLAB/outside TLAB、Object Count、Socket/Thread Park 等事件。

第四步,找大对象和高频分配。 批量查询、一次性 JSON、图片/报表、日志拼接、大 Map 缓存都要重点查。

第五步,先改业务分配。 降低单请求对象量,拆批处理,限制列表大小,避免把大对象放进缓存。

第六步,再灰度参数。 每次只改少数参数,保留改前改后 GC 日志和业务指标。

上线检查:别让调参变成新风险

  • 确认 -Xms-Xmx 与容器内存限制匹配,避免 OOMKill。
  • 确认 GC 日志滚动配置,别把磁盘写满。
  • 确认 p95/p99、GC pause、分配速率、Old 区占用都有告警。
  • 确认每次参数变更都有灰度和回滚方案。
  • 确认代码层面的分配优化已经验证,不把所有压力甩给 GC。

我的经验总结

G1 GC 很强,但它不是魔法。暂停变长时,先问业务最近是不是分配更多对象、响应更大、缓存更重、批处理更猛。GC 往往只是把应用的对象分配习惯暴露出来。

我的建议很简单:默认参数起步,日志和 JFR 先行,代码降分配优先,参数小步灰度。Java 生产优化最怕一把梭,最稳的是每一步都有证据。

声明:本文转载于:17golang Java频道原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>