面试记录 2026-09-21
题目:JVM 中一次完整的 GC 流程是怎样的?
难度:medium
标签:jvm/gc, general/knowledge, difficulty/medium
评分:⭐⭐☆☆☆(2/5)
我的回答
GC 的完整流程。GC 本质上就是垃圾的回收,前置是标记垃圾。一个对象它没有引用了,就会标记为被可被回收。首先这个先进行的是年轻代回收。一个对象,如果它能被回收,就在年轻代回收的时候回收掉。如果不能被回收,就将对象从年轻代转移到幸存者区、 Survivor 区。然后这时候它就经历了一次垃圾回收。后续第二次年轻代回收的时候,如果这个对象还存活着,它就从幸存者一区转移到幸存者二区,同步转移的还有在年轻代幸存的,还存活的区域,就整个都复制到幸存者二区。经历了反复几次。如果这个对象还存活,这个反复几次的数量可以在 CMS 的参数中调配,我记得默认是 15 次。如果这个对象还存活着,这个对象就会升级到老年代,这时候年轻代的垃圾回收已经不会对这个对象产生异常了,只有当老年代或者是方法区空间满了之后,才开始进行老年代的回收,也就是 FullGC,这时候如果它还存活,它还会像之前一样在 FullGC 中原地 FullGC 中存放,直到它被垃圾回收标记为垃圾,没有引用,进行被回收。这是常规的垃圾回收的升级换代方法。
点评
优点:
- Eden → From Survivor → To Survivor 交替的路径描述准确
- 默认 15 次晋升老年代(MaxTenuringThreshold)记忆正确,术语命中
- 提到了 Full GC 的触发条件(老年代/方法区满)
不足:
- 只描述了对象晋升流程,没描述 GC 算法流程——题目问"GC 流程"应包括标记→清理/复制/整理的具体步骤,回答只有对象搬迁
- 术语混淆:"15 次"是
MaxTenuringThreshold,不是 CMS 参数,是 HotSpot 通用配置
- 完全没提 Young GC 的算法:复制算法的具体动作(清空 Eden、存活复制到 To Survivor、From/To 切换、Eden/From 直接清零)
- 没提 Remark/写屏障/Remembered Set 这些并发 GC 的关键环节
- Full GC 描述不准:Full GC 不是"原地存放",而是整代回收+标记-整理(存活压缩到一侧);Mixed GC(G1)也会回收部分老年代,不是只有 Full GC 才动老年代
- 完全漏了并发 GC 阶段:Initial Mark → Concurrent Mark → Remark → Concurrent Sweep 这套流程没提到
- 没提 GC 触发条件:分配失败、
System.gc()、Metaspace 满、老年代不足等
补充:
- 对象晋升流程:Eden 满 → Young GC → Survivor 交替 → 达阈值晋升老年代
- 算法执行流程:Initial Mark (STW) → Concurrent Mark → Remark (STW) → Concurrent Sweep
- Young GC 具体动作:Eden 存活复制到 To Survivor,Survivor 存活对象按年龄晋升或复制到 To Survivor,清空 Eden + From Survivor,交换 From/To
- Full GC 具体动作:整代标记存活 → 标记-整理(存活压缩到一侧)→ 清理老年代尾部
- 触发条件:Eden 满(Young GC)、老年代满(Mixed/Full GC)、Metaspace 满(Full GC)、
System.gc()(建议性 Full GC)
- 观测:
-Xlog:gc* 看 STW 时长、-Xlog:gc+heap 看堆变化、JFR jdk.GCPhasePause
完整讲解
第一部分:知识重点剖析
核心考点定位:考察 GC 流程的双层理解——对象晋升路径 + 算法执行阶段,以及并发 GC 的关键环节。
知识点拆解:
- GC 流程的两层含义
- 对象生命周期流程:Eden → Survivor 交替 → 老年代 → 回收
- 算法执行流程:Initial Mark → Concurrent Mark → Remark → Concurrent Sweep
- 面试答"完整流程"要两层都说,只说一层扣分
- Young GC(Minor GC)完整流程
- 清空 Eden(不标不整理,直接归零)
- 将 Eden 存活对象复制到 To Survivor(复制算法)
- 将 From Survivor 存活对象按年龄判断:
- 未达阈值 → 复制到 To Survivor,年龄 +1
- 达到
MaxTenuringThreshold(默认 15)→ 晋升老年代
- Survivor 装不下 → 空间分配担保:能塞老年代就塞,塞不下晋升失败
- 交换 From 和 To 指针
- 清空 From Survivor
- 特点:STW 短(几十 ms),频率高(每秒数次)
- Full GC 完整流程(CMS 为例,G1 有 Mixed GC)
- 触发:老年代满、Metaspace 满、
System.gc()、Survivor 晋升失败
- CMS 四阶段:
- Initial Mark(初始标记):STW,标 GC Roots,很快
- Concurrent Mark(并发标记):GC 线程 + 用户线程并发遍历,不 STW
- Remark(重新标记):STW,处理并发标记期间的引用变动(靠写屏障 / Card Table 缩短)
- Concurrent Sweep(并发清除):并发清理死对象,不 STW(有碎片)
- Full GC 传统流程(非并发):STW 整代标记 → 标记-整理(存活压缩到一侧)→ 清理尾部
- Mixed GC(G1 特有)
- Young GC + 部分老年代 Region(按"脏度"排序优先回收)
- 通过
-XX:MaxGCPauseMillis 目标停顿预测要回收哪些 Region
- 避免 Full GC(G1 尽量避免退化到 Full GC)
- 关键机制:写屏障 + Remembered Set
- 老年代引用新生代对象时,写屏障在赋值时更新 Card(512 字节一片)
- GC 时只处理"脏" Card,避免全堆扫描
- G1 用 Remembered Set 记录跨 Region 引用
- 观测与调优
-Xlog:gc*(JDK 9+):完整 GC 日志,含 STW 时长
-Xlog:gc+heap:堆各区变化
- JFR:
jdk.GCPhasePause、jdk.GCHeapSummary、jdk.GCObjectAllocationInNewTLAB
- Arthas
dashboard、Prometheus + JVMExporter
- 生产告警:单次 STW > 200ms、频繁 FullGC、Metaspace 增长
第二部分:考察方向与延伸递进
考察方向:
- 深度:写屏障/Remembered Set/Eden:From:To 交换、Remark 如何缩短
- 广度:不同 GC 器(CMS/G1/ZGC)的流程差异、ZGC 无 Full GC 的意义
- 应用:GC 日志分析、老年代 OOM 排查、晋升失败的处理
延伸递进面试题:
- Young GC 具体动作是什么?为什么 Eden/From 直接清零?
- 解答要点:Eden 里没存活的不复制,存活全复制到 To Survivor;Eden/From 已"清空",无需标记,直接归零最快
- Remark 为什么需要 STW?如何缩短?
- 解答要点:Remark 要处理并发标记期间的引用变动,必须 STW 保证一致性;通过写屏障让变动可追踪,Remark 只处理"脏"引用(Card Table / Remembered Set)
- G1 如何避免 Full GC?
- 解答要点:用 Mixed GC(Young + 部分老年代 Region)替代 Full GC;
-XX:MaxGCPauseMillis 目标停顿;-XX:InitiatingHeapOccupancyPercent 阈值控制启动并发标记的时机
- 如何在生产上观察 GC 流程?
- 解答要点:
-Xlog:gc* 完整日志、JFR 事件、Arthas dashboard、关注 Pause Remark 和 Full GC 频率
- ZGC 的 GC 流程和传统 GC 有什么本质不同?
- 解答要点:无 Full GC 概念;并发标记+并发整理都用读屏障;多映射让对象移动时改映射而非改引用;STW 只有 Initial Mark 和 Final Relocate 两个瞬间
第三部分:面试者参考回答(300-600 字口语化)
面试官您好,GC 的"完整流程"其实有两层含义,我按层说。
第一层是对象晋升流程。新对象先在 Eden 分配,Eden 满触发 Young GC:Eden 里的存活对象复制到 To Survivor,From Survivor 里的对象年龄 +1 后继续留 Survivor 或晋升老年代,然后交换 From 和 To 的指针,清空 Eden 和 From。反复几次 Young GC,对象年龄累积超过 MaxTenuringThreshold(默认 15)就晋升老年代。这一步的核心是复制算法,靠 Survivor 交替避免碎片。
第二层是 GC 算法执行流程,以 CMS 为例(G1 有 Mixed GC,ZGC 无 Full GC):
- Initial Mark(初始标记):STW,只标 GC Roots,非常快
- Concurrent Mark(并发标记):GC 线程和用户线程并发遍历引用图,不 STW
- Remark(重新标记):STW,处理并发标记期间的引用变动,靠写屏障 + Card Table / Remembered Set 缩短范围
- Concurrent Sweep(并发清除):并发清理死对象,不 STW(有碎片问题)
如果是 Full GC(非并发),流程是 STW 整代标记 → 标记-整理(存活压缩到一侧)→ 清理尾部。Full GC 触发条件:老年代满、Metaspace 满、System.gc()、Survivor 晋升失败。
G1 的关键差异:用 Mixed GC(Young + 部分老年代 Region)替代 Full GC,通过 -XX:MaxGCPauseMillis 目标停顿预测要回收哪些 Region,尽量避免退化到 Full GC。
贯穿全程的机制是写屏障 + Remembered Set:老年代引用新生代对象时,写屏障在赋值时更新 Card,GC 时只处理"脏"的引用,避免全堆扫描。
生产上怎么观察? 用 -Xlog:gc* 打完整 GC 日志看 STW 时长,JFR jdk.GCPhasePause 看各阶段耗时,关注 Pause Remark 和 Full GC 频率;生产上单次 STW > 200ms 或频繁 FullGC 都应告警。
一句话总结:GC 流程 = 对象晋升(Eden → Survivor → Old)+ 算法执行(Initial Mark → Concurrent Mark → Remark → Sweep),前者决定"对象去哪",后者决定"GC 怎么算"。