垃圾回收算法
候选人小孙在面试字节 P6 时,面试官问道:
"JVM 有哪些垃圾回收算法?"
小孙说:"有标记-清除、复制、标记-整理..."面试官追问:"标记-清除有什么缺点?"
小孙说:"会产生内存碎片..."面试官继续追问:"为什么年轻代用复制算法而老年代用标记-整理?"
小孙答不上来...
一、核心问题:垃圾回收算法 🔴
1.1 问题拆解
1.2 ❌ 错误示范
候选人原话 A:"标记-清除是最常用的算法。"
问题诊断:标记-清除有内存碎片问题,通常用于老年代的 CMS(并发标记-清除)。
候选人原话 B:"复制算法浪费一半空间。"
问题诊断:复制算法的空间利用率取决于对象存活率。年轻代 80% 的对象朝生夕死,存活率极低,所以复制算法在年轻代非常高效。
1.3 标准回答
P5 级别:四种基础算法
1. 标记-清除(Mark-Sweep):
步骤:先标记所有可达对象,再清除所有未标记对象。
缺点:产生内存碎片,分配速度慢。
2. 复制算法(Copying):
将内存分为两块,每次只使用一块。GC 时,将存活对象复制到另一块,然后清除当前块。
优点:无碎片,分配简单(移动指针)。
缺点:空间利用率低(50%)。
3. 标记-整理(Mark-Compact):
先标记存活对象,然后将所有存活对象向一端移动,清理边界外的内存。
优点:无碎片,空间利用率高。
缺点:移动对象成本高(需要更新引用)。
P6 级别:分代策略
为什么年轻代用复制算法?
根据弱分代假说:大多数对象朝生夕死。年轻代 80%~90% 的对象在第一次 Minor GC 时就被回收,存活对象很少,复制开销小。
年轻代的 Survivor 区设计就是复制算法的应用:
为什么老年代用标记-整理?
老年代对象存活率高(大多数对象会活很久),复制算法的复制开销太大(每次需要复制大量对象)。
老年代使用标记-整理算法(或标记-清除 + 整理):
P7 级别:现代 GC 算法的演进
CMS(Concurrent Mark Sweep):
CMS 是一种并发的标记-清除算法,目的是减少停顿时间:
G1(Garbage First):
G1 将堆划分为多个大小相等的 Region(通常是 1MB),使用标记-整理算法。G1 的优势是可预测的停顿时间(通过
-XX:MaxGCPauseMillis=200设置)。ZGC(Z Garbage Collector):
ZGC 使用着色指针(Colored Pointers)和读屏障,实现了停顿时间 < 1ms 的目标,不分代,全堆并发回收。
【面试官心理】 这道题我能问到 P7 级别,是因为现代 GC 算法的演进涉及了并发优化、停顿时间控制等多个维度。能说清 CMS 的并发阶段和 G1 的停顿时间可预测性的候选人说明他对现代 GC 设计有了解。
1.4 追问升级
追问:标记阶段和并发标记的区别?
标记阶段(Stop-The-World):JVM 停止所有应用线程(Stop The World),进行 GC Roots 的可达性分析。
并发标记:应用线程和 GC 线程同时运行,GC 线程追踪引用变化。优点是不停顿,缺点是需要处理并发修改(浮动垃圾)。
二、生产避坑 🟡
2.1 CMS 的内存碎片问题
CMS 使用标记-清除,不整理内存。长期运行后会产生大量内存碎片。当碎片过多时,CMS 会触发 Full GC(Serial Old) 进行压缩,停顿时间很长。
2.2 G1 的 Region 设计
G1 将堆分为多个 Region,每个 Region 可以是 Eden/Survivor/Old/Humongous。Humongous 区用于超过 Region 50% 的大对象。
面试加分点:能说出"Shenandoah GC(JDK 12 GA)和 ZGC 一样是低停顿 GC,但 Shenandoah 的着色指针在对象头上(JDK 12+),不需要读取 Java 堆内存就能判断对象状态",说明他关注了现代 GC 的演进。
面试陷阱:被问到"G1 是分代收集器吗",很多人会说"不是"。准确答案是:G1 是分代的,但它的分代是逻辑上的(通过不同的 Region 类型),而非物理上的固定区域。