
一句话JVM 根据大部分对象朝生夕死的规律把堆分成了新生代和老年代不同代用不同的回收策略。理解对象在堆里的流转过程就掌握了 GC 的核心。为什么堆要分代因为大部分对象活不久你的项目里 查一次列表 → new 一堆 User 对象 → 用完没人引用了 下一次请求 → 又 new 一堆 → 又没人引用了 → 90% 的对象活不过几毫秒如果不分代每次回收都要扫描整个堆效率极低。分代后新生代空间小回收快Minor GC毫秒级老年代放长命对象回收频率低Full GC秒级堆的分区结构堆Heap │ ├── 新生代Young Generation │ ├── EdenE 甸园 ← 占 80% │ └── Survivor 区 │ ├── S0From ← 占 10% │ └── S1To ← 占 10% │ └── 比例Eden : S0 : S1 8 : 1 : 1 │ └── 老年代Old Generation默认新生代和老年代比例约 1 : 2可调。对象完整流转过程new User() → 放进 Eden 区 │ ├── Eden 快满了 → Minor GC │ │ │ ├── 没人引用的对象 → 回收 │ └── 还有人引用的对象 → 移到 S0年龄1 │ ├── Eden 再次满了 → Minor GC │ │ │ ├── Eden 存活对象 S0 存活对象 → 移到 S1年龄1 │ └── S0 清空 │ ├── 反复如此Eden 正在使用的 Survivor → 另一个 Survivor │ S0 和 S1 始终保持一个为空 │ ├── 对象年龄达到阈值默认 15→ 升到老年代 │ ├── 或者Survivor 区装不下了 → 动态年龄判定 → 年龄大的提前升老年代 │ └── 老年代满了 → Full GC │ ├── 扫描整个堆新生代 老年代 元空间 └── STWStop The World所有线程暂停Minor GC vs Full GC对比项Minor GCYoung GCFull GC回收范围新生代Eden Survivor整个堆 元空间触发条件Eden 区满了老年代满了 / 元空间满了 / 主动调用 System.gc()STW 时长几毫秒用户基本无感知几秒甚至几十秒系统卡顿频率频繁少但每次都很重注意Minor GC 也会 **STWStop The World**——暂停所有线程。但因为只扫新生代范围小所以很快。Full GC 是 JVM 调优的主要目标——要尽量降低 Full GC 的频率和时长。GC Roots 和可达性分析JVM 判断对象该不该回收用的是可达性分析从 GC Roots肯定活着的起点出发 能走到的对象 → 活着 ❌ 不回收 走不到的对象 → 不可达 → 回收 ✅ GC Roots 包括 ├── 栈帧中的局部变量引用方法里正在用的对象 ├── 静态变量引用static 修饰的 └── 活跃线程打个比方一栋楼堆里有 1000 个房间 你不需要查每个房间有没有人 你只需要从 3 个值班室GC Roots出发 前台有人的 → 她旁边的房间也有人 紧急指示灯亮着 → 它的线路都有人维护 监控室有人 → 监控覆盖的区域都是活的 能走到的房间 → 有人 → 不回收 走不到的房间 → 没人 → 回收面试问答Full GC 频繁怎么排查① 看监控老年代占用率持续上升 ② 加 JVM 参数-XX:HeapDumpOnOutOfMemoryError ③ OOM 时拿到 hprof 堆转储文件 ④ MAT 分析看占内存最大的对象 ⑤ 定位代码顺着线程栈找到问题 SQL/代码 常见原因 - SQL 没有分页一次查太多数据 - 定时任务频率太高 - 内存泄漏对象只增不减一个对象一定会熬到 15 岁才进老年代吗不一定。还有动态年龄判定如果 Survivor 区装不下了会把年龄最大的那批对象提前升到老年代不一定要等到 15 岁。参考来源《深入理解 Java 虚拟机》第 3 章JDK8 G1 GC 文档