Java垃圾回收需通过“观察—验证—调优”三步闭环实践掌握:一、用启动参数输出GC日志并固定堆大小,结合对象分配触发Minor GC;二、用jstat、VisualVM、JMC等工具可视化分析内存与停顿;三、刻意制造内存泄漏、大对象冲击等典型问题并排查;四、调参遵循单变量原则,优先换收集器,再设目标,最后微调区域。

Java 垃圾回收(GC)不是靠背概念通关的,而是靠“观察—验证—调优”三步闭环练出来的。重点不在记住算法名字,而在看懂 JVM 实际怎么回收、为什么停顿、哪里卡住了。
一、从启动参数开始动手,让 GC “说话”
不加任何参数运行 Java 程序,等于闭着眼开车。必须让 JVM 输出 GC 日志,这是所有演练的起点:
- 加上 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log(JDK 8 及以前)或 -Xlog:gc*:gc.log(JDK 9+)
- 配合 -Xms256m -Xmx256m 固定堆大小,排除扩容干扰
- 写一个简单循环持续 new 对象(比如
new byte[1024*1024]),强制触发 Minor GC,观察日志里 Eden 区怎么清、Survivor 怎么复制、多少对象晋升到老年代
二、用工具看内存“实况”,别只盯日志
日志是文字报告,可视化工具才是“望远镜”和“显微镜”:
-
jstat -gc
1s :实时看各代容量、已用空间、GC 次数与耗时,确认是否频繁 Minor GC 或意外触发 Full GC - jvisualvm + VisualGC 插件:图形化看 Eden/Survivor/老年代水位线跳动,对象年龄分布直方图,一眼识别对象“该死不死”(如本该短命却早早晋升)
- JMC(Java Mission Control):录制飞行记录(JFR),分析 GC 停顿根源——是并发标记慢?还是 Remark 阶段卡住?
三、刻意制造典型问题,再亲手解决
照着真实场景练,比做题有效十倍:
立即学习“Java免费学习笔记(深入)”;
- 内存泄漏演练:用 static Map 不断 put 对象却不 remove,观察老年代缓慢上涨、Full GC 越来越频繁,用 jmap -histo 找出最多实例的类
- 大对象冲击演练:分配超过 -XX:PretenureSizeThreshold 的数组(如 new byte[8*1024*1024]),看它直接进老年代,是否引发提前 Full GC
- Stop-The-World 体验:用 Serial GC 运行高吞吐程序,用 -XX:+PrintGCTimeStamps 对比 GC 前后时间戳,亲自感受毫秒级暂停对响应的影响
四、调参不是玄学,每次只改一个变量
调优的本质是控制权衡:吞吐量 vs 延迟 vs 内存 footprint。不要一上来就堆参数:
- 先换收集器:从默认的 Parallel 换成 G1(-XX:+UseG1GC),观察是否减少大停顿
- 再调目标:加 -XX:MaxGCPauseMillis=200 让 G1 主动压缩、降低单次停顿(但可能增加 GC 频率)
- 最后微调区域:发现 Humongous 区对象太多?调 -XX:G1HeapRegionSize;Survivor 太小导致过早晋升?调 -XX:SurvivorRatio
练熟之后,你看到一行 GC 日志就能脑补出堆内存快照,听到“服务卡顿”第一反应是查 GC 停顿曲线。这不是魔法,是反复看见、动手、修正的结果。


















