⭐ 推荐:社区规则条款 V1.0

Java 的垃圾回收调优实战?

萝卜特棒
萝卜特棒 正式会员萌新站长
发布于 2026-10-19 08:54 ·1 浏览 ·0 回复
内容摘要

Java GC 调优应先开启 GC 日志并量化吞吐、延迟和内存指标,再按场景选择 G1、Parallel 或 ZGC;G1 主要设置 -Xms 与 -Xmx 相等及 MaxGCPauseMillis,频繁 Full GC 需依据日志触发原因

结论:Java GC 调优的成败 90% 取决于“选对收集器”和“把堆设对”这两件事,参数微调只影响最后 10%;并且在拿到 GC 日志之前,任何参数调整都只是猜测,不算调优。

GC 调优的第一步应该做什么?

结论:第一步是加 GC 日志并量化指标,不能直接改参数。先跑一份线上真实负载的日志,再决定是否调整。

JDK 9 及以上使用统一日志:

-Xlog:gc*,gc+heap=debug:file=/var/log/gc.log:time,uptime,level,tags

JDK 8 用旧参数:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/gc.log

同时用 jstat -gcutil <pid> 1000 5 每秒采样一次,重点看 YGC/YGCT、FGC/FGCT 和 O(老年代使用率)三列。要量化的目标只有三个:吞吐(GC 总耗时占比,普通服务控制在 5% 以内)、延迟(单次 STW 的 P99 值)、内存占用。指标定不下来,后面所有参数都没有评判标准。

G1、Parallel、ZGC 该怎么选?

结论:JDK 9 起默认收集器已经是 G1,绝大多数在线服务不用换;纯批处理用 Parallel,追求极低停顿用 ZGC。

  • 在线服务、堆 4GB~64GB:G1 即可,JDK 9 到 JDK 21 都是默认值,无需显式指定。
  • 离线批处理、堆小于 8GB、吞吐优先:-XX:+UseParallelGC,它没有并发标记阶段,CPU 利用率最高。
  • 延迟敏感(单次停顿要求 10ms 以内)、堆几十 GB:ZGC,JDK 11 实验性引入、JDK 15 转正,JDK 21 起提供分代 ZGC(-XX:+UseZGC -XX:+ZGenerational)。
  • CMS 在 JDK 9 被标记废弃、JDK 14 已彻底移除,新项目不要再考虑。

G1 的关键参数怎么设?

结论:G1 上真正需要设的只有 -Xms、-Xmx 相等,再加一个 -XX:MaxGCPauseMillis,其余保持默认。

-Xms8g -Xmx8g -XX:MaxGCPauseMillis=100

-Xms 与 -Xmx 设成相同值,可以避免堆动态伸缩带来的额外停顿。-XX:MaxGCPauseMillis 是软目标,默认 200ms;把它调小的直接代价是 Young 区变小、Young GC 更频繁,吞吐下降,所以不要低于 50ms。另外注意:G1 下设置 -Xmn 会被忽略并打印警告,年轻代大小由收集器自己算。-XX:G1HeapRegionSize 默认按堆大小除以 2048 推导,取值 1MB~32MB 且必须是 2 的幂,一般不用动。

Full GC 频繁或单次停顿很长,怎么排查?

结论:先读 GC 日志里的触发原因,不同原因对应完全不同的改法。

常见三类:

  1. Metadata GC Threshold:Metaspace 扩容引发 Full GC。加 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m 固定初始阈值即可。
  2. Humongous Allocation:单个对象超过 region 一半即被判为大对象,直接进老年代。要么减小大数组/大字符串(如把一次查 10 万条改成分批 5000 条),要么调大 -XX:G1HeapRegionSize。
  3. to-space exhausted 或 Full GC 后老年代仍占 70% 以上:这是内存泄漏信号,不是参数问题,用 jmap -dump:live,format=b,file=heap.hprof <pid> 抓堆转储分析。同时常备 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap.hprof。

哪些“GC 调优”其实是坑?

结论:以下四种常见做法,副作用往往大于收益。

第一,-XX:+DisableExplicitGC 会让 System.gc() 失效,而 NIO 的 DirectByteBuffer 正是靠它兜底回收堆外内存,禁用后可能报 OutOfMemoryError: Direct buffer memory。第二,盲目加大堆:堆越大,Full GC 单次停顿越长、G1 标记阶段也越久,合理参考是 Full GC 后老年代存活对象占堆的 30%~50%。第三,把 Young GC 次数当问题——Young GC 单次通常只有十几到几十毫秒,频率高不等于有病。第四,随手把 -XX:MaxTenuringThreshold 调到 15,会让本该早回收的对象堆在 Survivor 里,反而推高晋升量。

收束一句:先看日志定指标,选对收集器、堆设到位,再用 MaxGCPauseMillis 这类软目标微调;参数之外,减少对象分配永远是收益最高的一步。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2285.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~