Java垃圾回收需实战掌握:先通过可达性分析判定对象生死,再理解堆内存分代结构(Eden/S0/S1/Old)及对象晋升规则,接着按场景选配回收器(Parallel/G1/ZGC等),最后用jstat/jmap/jstack等工具链诊断根因。

Java 垃圾回收不是背概念就能通关的,得动手拆、动脑判、动脚踩坑。核心就三件事:**看懂对象怎么死、搞清内存怎么分、选对回收器怎么配**。下面直奔实战要点,不绕弯。
一、先练“对象生死判定”——别信引用计数,盯紧 GC Roots
Java 不用引用计数,因为它会被循环引用骗(比如 A 引用 B、B 又引用 A,但外部已无任何引用)。真正起作用的是可达性分析:从一组固定起点(GC Roots)出发,顺着引用链往下找,断链即“死亡”。
你得亲手画出 GC Roots 的实际路径,比如:
- 栈帧里的局部变量(如
User user = new User()中的user) - 静态字段(如
public static List)cache = new ArrayList(); - 常量池里的字符串字面量(如
String s = "hello") - JNI 调用中本地方法持有的 Java 对象引用
建议写个小 demo,用 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 启动,再手动置 null + System.gc(),观察日志里哪些对象真被回收了——这比看图理解快十倍。
二、再练“堆内存分代结构”——新生代不是一块铁板
HotSpot 默认把堆切成新生代(Young)和老年代(Old),而新生代又细为:Eden(80%)、S0(10%)、S1(10%)。这不是理论比例,是真实内存布局,影响每次 Minor GC 的行为。
关键要练出肌肉记忆:
- 新对象一律分配在 Eden;Eden 满 → 触发 Minor GC
- 存活对象从 Eden 和 From 区复制到 To 区;To 区满则部分晋升老年代
- 对象每熬过一次 Minor GC,年龄 +1;默认年龄 ≥15 就进老年代(可通过
-XX:MaxTenuringThreshold调) - 大对象(如超大数组)可能直接进老年代(
-XX:PretenureSizeThreshold可控)
用 jstat -gc 实时看 Eden/S0/S1/Old 的使用率变化,比读文档更直观。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
三、实操“回收器选择与调优”——别硬背参数,先看卡点
不同回收器解决不同问题,选错等于自缚手脚:
-
吞吐量优先(后台批处理)→ 用 Parallel Scavenge + Parallel Old,配
-XX:GCTimeRatio=99(目标 GC 时间占比 ≤1%) -
延迟敏感(Web API、实时系统)→ JDK8 用 CMS(注意已废弃),JDK9+ 用 G1,开
-XX:MaxGCPauseMillis=200设目标停顿 -
超大堆 + 极低延迟(百 GB 堆、毫秒级 STW)→ JDK11+ 用 ZGC 或 Shenandoah,只需
-XX:+UseZGC,无需调参
调优不是堆参数,而是看现象反推:如果频繁 Minor GC 但老年代缓慢上涨 → 可能是对象“早熟”,该调高晋升阈值或检查是否有长生命周期集合缓存;如果 Full GC 频发 → 先查是否内存泄漏(用 jmap -histo 看 top 类实例数),再看是不是老年代真的不够用。
四、最后练“诊断工具链”——让 GC 自己开口说话
光看日志不够,得组合工具定位根因:
-
jstat -gcutil:每秒刷新 GC 统计,盯住 YGC/YGCT/FGC/FGCT1000 -
jmap -histo:live:列出当前存活对象数量和大小,快速发现异常膨胀类 -
jstack:查线程阻塞,排除 GC 外的卡顿干扰 - 可视化:用 VisualVM 或 JConsole 连接运行中的 JVM,拖拽看堆内存曲线、GC 时间热力图
真正硬核,是能把一段 OOM 日志还原成业务代码场景:比如看到老年代持续增长 + Full GC 后几乎不降,大概率是静态 Map 缓存没清理;看到 Eden 区刚清完立刻又满,可能是短生命周期对象暴增(比如日志拼接、JSON 解析临时对象)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










