JVM GC日志是定位高并发系统性能瓶颈最直接、最可靠的线索,需重点关注Young GC频率与耗时、Full GC频次与停顿、内存使用趋势三组量化指标,并配合-XX:+PrintGCDetails等参数生成结构化日志,结合jstat实时验证,精准识别内存泄漏、提前晋升或配置失效等问题。

JVM GC 日志是定位高并发系统性能瓶颈最直接、最可靠的线索之一。它不依赖业务代码逻辑,也不受网络或数据库干扰,能真实反映内存分配压力与回收行为是否健康。用对了,往往几分钟就能锁定问题根因;用错了,可能把调参当治病,越调越乱。
关键指标要盯紧:不是看日志,而是看数字
GC 日志本身是文本,但真正有价值的是其中可量化的指标。重点关注三组数据:
- Young GC 频率与耗时:正常应 >5 秒一次,单次
- Full GC 频率与停顿:生产环境理想状态是几小时甚至几天一次。若达到每分钟 ≥2 次,或单次停顿 >1s,基本可判定老年代压力过大——可能是内存泄漏、大对象直入老年代、或晋升阈值过低导致对象提前进入老年代。
- 内存使用趋势:观察每次 GC 后老年代(O)和元空间(M)的占用是否持续上涨。比如 O 区从 30% → 60% → 90% 缓慢爬升,且 Full GC 后无法回落,大概率存在内存泄漏。
日志配置必须带上下文:光有 -XX:+PrintGCDetails 不够
只开基础参数,等于只拍一张模糊快照。高并发场景下,需要结构化、带时间锚点、可追溯的完整日志:
- 必加参数:-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution
- 生产建议加滚动:-Xloggc:/var/log/app/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=100M
- 特别提醒:-XX:+PrintTenuringDistribution 能显示每次 Young GC 后各年龄对象分布,帮你判断“晋升阈值是否被动态下调”——这是很多“提前晋升”问题的唯一证据。
结合 jstat 实时交叉验证:别等日志写完再分析
GC 日志是事后回溯,而 jstat 是实时听诊器。上线后立刻执行:
-
jstat -gcutil
1s 10 :看 S0/S1/E/O/M 的百分比变化节奏。若 E 区刚清空又立刻涨到 95%,说明对象生成速率远超回收能力。 -
jstat -gccapacity
:确认实际生效的堆大小、新生代占比是否与启动参数一致(常有配置未生效或被容器限制覆盖的情况)。 - 对比关键字段:YGCT/FGCT/GCT 占总运行时间比例。若 GCT > 10%,说明 GC 已严重挤压业务线程时间,必须干预。
定位到根因后,别急着调堆大小
盲目加大 -Xmx 往往掩盖问题,甚至恶化延迟。先问清楚:
- 是对象创建太快(比如循环里 new HashMap)?→ 改代码复用对象或用 ThreadLocal。
- 是缓存没设上限(ConcurrentHashMap 不断 put)?→ 加容量控制或过期策略。
- 是 CMS 触发过晚(-XX:CMSInitiatingOccupancyFraction=72 太高)?→ 降到 65 并观察是否减少 Full GC。
- 是 G1 的大对象(Humongous)频繁分配?→ 检查是否有 > 50% region 大小的 byte[] 或 char[],考虑拆分或改用池化。
调优不是调参数,是调行为。GC 日志告诉你“哪里疼”,但得靠代码、架构、监控一起诊断“为什么疼”。











