排查gc性能卡点需结合收集器特性分析日志与指标:先确认收集器类型,解读其日志关键字段(如g1的mixed gc频率、cms的concurrent mode failure),再通过jstat或prometheus观察eden/老年代/metaspace内存变化节奏,最后按业务场景选择适配收集器并验证真实负载表现。

排查GC导致的性能卡点,核心是看GC行为是否与应用负载匹配——不是“有没有GC”,而是“GC是否在不该发生的时候频繁发生、停顿是否超出容忍阈值、内存是否持续高位不释放”。不同收集器(如Serial、Parallel、CMS、G1、ZGC)的设计目标和触发逻辑差异很大,不能套用同一套分析思路。
看日志:先确认用的是哪个收集器,再读懂它的日志语义
启动参数里带 -XX:+PrintGCDetails -XX:+PrintGCDateStamps 是基础。但光有日志不够,得知道每种收集器日志的关键字段含义:
- G1的日志里重点关注 Pause Young (Mixed) 和 Concurrent Cycle 的耗时与频率;Mixed GC频繁说明老年代晋升太快或Region碎片化严重
- Parallel GC日志中出现大量 PSYoungGen + ParOldGen 组合回收,且Full GC间隔短,大概率是老年代空间不足或对象过早晋升
- CMS日志里若频繁出现 concurrent mode failure,说明并发标记赶不上对象分配速度,需调大老年代或降低晋升阈值
- ZGC日志中 Pause Mark Start / Pause Relocate Start 应稳定在毫秒级;若某次Pause明显拉长,要结合堆外内存或TLAB分配异常排查
抓指标:不只是GC次数和时间,更要盯住内存变化节奏
用 jstat -gc
- 年轻代Eden使用率是否在每次YGC前都接近100%?如果不是,说明对象存活率高或Survivor空间太小,导致提前晋升
- 老年代使用量是否呈阶梯式缓慢上升?这往往是内存泄漏信号;若每次Full GC后仍残留大量不可达对象,可能是finalize未执行完或JNI引用未清理
- Metaspace使用量持续增长且不下降,配合java.lang.OutOfMemoryError: Metaspace,大概率是动态类加载(如Groovy脚本、OSGi)未卸载
对场景:按业务特征选收集器,别让GC策略和流量模式硬扛
高吞吐批处理任务适合Parallel GC,低延迟API服务优先考虑G1或ZGC,但落地前必须验证真实负载下的表现:
- 突发流量下G1的Mixed GC可能堆积,建议开启 -XX:G1HeapWastePercent=5 控制垃圾占比阈值,避免过早触发Mixed
- ZGC虽标称“停顿-XX:+UseLargePages -XX:+UnlockExperimentalVMOptions -XX:+UseZGC
- CMS已废弃,但存量系统若仍在用,务必关闭 -XX:+ExplicitGCInvokesConcurrent,防止System.gc()误触发并发周期干扰业务线程
做验证:改配置≠见效,必须用压测对比基线
任何GC参数调整后,都要在同一环境、相同数据集、相同QPS下做AB压测:
- 对比维度不止是平均RT,还要看TP99/TP999延迟毛刺、GC总暂停时间占比(建议
- 例如将G1的 -XX:MaxGCPauseMillis=200 改为100,可能换来更多更碎的GC,反而增加CPU开销,需看整体吞吐是否受损
- 升级JDK版本带来的GC改进(如JDK17的ZGC Production Ready、JDK21的Epsilon退役),要同步验证JIT编译行为变化对GC压力的影响











