精通Java垃圾收集器的核心在于机制理解、数据驱动调优和证据链式故障定位:能说清对象晋升的动态年龄策略,用jstat/jmap等工具实操验证GC行为,基于P99延迟等指标闭环调优,并通过GC日志、堆直方图与支配树分析精准定位内存问题,而非死记参数。

面试中谈到“精通Java垃圾收集器”,不是背熟CMS、G1、ZGC的参数列表,而是能说清为什么选它、怎么验证它、出问题时怎么切它。核心能力体现在三个层面:机制理解不浮于表面、调优有数据支撑、故障定位有路径。
能画出GC触发与内存流动的真实路径
很多候选人知道“新生代用复制算法”,但说不清对象从new到晋升再到老年代的具体条件。比如:不是所有Survivor区满才晋升,而是动态年龄判断——当某年龄对象累计占Survivor空间50%以上,该年龄及更老的对象直接进入老年代。这直接影响大对象分配策略和Minor GC频率。
建议:用JDK自带工具实操一次完整流程:
– 启动应用加 -XX:+PrintGCDetails -Xloggc:gc.log
– 用jstat持续观察 jstat -gc <pid> 1s
– 手动触发几次分配压力(如循环创建1MB byte[]),对照日志看Eden填满→Minor GC→存活对象进入Survivor→年龄增长→晋升阈值触发
调优不是调参数,是建指标闭环
真正“精通”的人不会一上来就改-XX:MaxGCPauseMillis。他会先定义目标:是降低P99延迟?还是减少GC总耗时占比?再选观测指标:GC吞吐量(GC time / total time)、平均停顿(Avg GC pause)、晋升率(Old Gen growth per minute)。
常见误区:
– 把Young GC频繁归因为堆太小,其实可能是对象生命周期过长或缓存未清理
– 看到Full GC就换G1,却没查是否因System.gc()被显式调用或堆外内存泄漏导致
– 用ZGC却忽略Linux大页未开启、或JDK版本不匹配(ZGC在JDK11为实验特性,JDK17才正式支持)
故障定位靠证据链,不是靠猜测
线上GC异常(如停顿飙升、频繁Full GC、老年代持续增长),要能快速构建证据链:
– 第一步:确认是否内存泄漏 → jmap -histo查看对象数量/占用,重点关注业务类、集合类、ThreadLocalMap
– 第二步:确认GC策略是否被干扰 → jinfo -flag +PrintGCDetails 查运行时参数,对比启动参数是否被覆盖
– 第三步:看GC日志时间戳对齐系统负载 → 用zgrep “GC pause” gc.log | awk ‘{print $1,$2}’ 查停顿是否集中在DB慢查询、批量任务时段
– 第四步:必要时做堆转储 → jmap -dump:format=b,file=heap.hprof <pid>,用Eclipse MAT分析支配树(Dominator Tree)找根因对象
能讲清不同收集器的取舍本质
G1不是“万能替代CMS”,ZGC也不是“零停顿银弹”。关键要看场景约束:
– G1适合4~64GB堆,但混合GC依赖Remembered Set维护成本,Region大小设错会导致大量Humongous对象分配失败
– CMS已废弃,但理解其“并发标记-清除”逻辑,有助于识别G1/ZGC中并发阶段的瓶颈点(如并发标记耗时突增,往往因堆内引用关系复杂或CPU资源争抢)
– ZGC停顿可控在10ms内,但要求JDK11+、Linux/x64、且需关闭Transparent Huge Pages(THP),否则反而引发卡顿


















