垃圾回收算法

候选人小孙在面试字节 P6 时,面试官问道:

"JVM 有哪些垃圾回收算法?"

小孙说:"有标记-清除、复制、标记-整理..."面试官追问:"标记-清除有什么缺点?"

小孙说:"会产生内存碎片..."面试官继续追问:"为什么年轻代用复制算法而老年代用标记-整理?"

小孙答不上来...

一、核心问题:垃圾回收算法 🔴

1.1 问题拆解

第一层:四种算法(有哪些?)
  "JVM 使用的四种 GC 算法是什么?"
  考察点:标记-清除/复制/标记-整理/分代收集

第二层:原理与优缺点(怎么工作?)
  "每种算法的原理是什么?优缺点是什么?"
  考察点:内存效率、碎片、效率

第三层:分代策略(怎么组合?)
  "为什么年轻代用复制算法而老年代用标记-整理?"
  考察点:对象存活率、分代假说

1.2 ❌ 错误示范

候选人原话 A:"标记-清除是最常用的算法。"

问题诊断:标记-清除有内存碎片问题,通常用于老年代的 CMS(并发标记-清除)。

候选人原话 B:"复制算法浪费一半空间。"

问题诊断:复制算法的空间利用率取决于对象存活率。年轻代 80% 的对象朝生夕死,存活率极低,所以复制算法在年轻代非常高效。

1.3 标准回答

P5 级别:四种基础算法

1. 标记-清除(Mark-Sweep)

graph TD
    A[标记阶段] --> B[清除阶段]
    A -->|可达对象| C[标记的对象]
    A -->|不可达对象| D[未标记的对象]
    D -->|清除| E[空闲内存]
    C -->|保留| F[存活对象]
    G[碎片化的内存]

步骤:先标记所有可达对象,再清除所有未标记对象。

缺点:产生内存碎片,分配速度慢。

2. 复制算法(Copying)

将内存分为两块,每次只使用一块。GC 时,将存活对象复制到另一块,然后清除当前块。

优点:无碎片,分配简单(移动指针)。

缺点:空间利用率低(50%)。

3. 标记-整理(Mark-Compact)

先标记存活对象,然后将所有存活对象向一端移动,清理边界外的内存。

优点:无碎片,空间利用率高。

缺点:移动对象成本高(需要更新引用)。

P6 级别:分代策略

为什么年轻代用复制算法?

根据弱分代假说:大多数对象朝生夕死。年轻代 80%~90% 的对象在第一次 Minor GC 时就被回收,存活对象很少,复制开销小。

年轻代的 Survivor 区设计就是复制算法的应用:

graph TD
    E[Eden 区] -->|Minor GC| S0[Survivor S0<br/>存活对象]
    S0 -->|下一次 Minor GC| S1[Survivor S1]
    S1 -->|下一次 Minor GC| O[老年代]
    O -->|年龄达到阈值| O

为什么老年代用标记-整理?

老年代对象存活率高(大多数对象会活很久),复制算法的复制开销太大(每次需要复制大量对象)。

老年代使用标记-整理算法(或标记-清除 + 整理):

区域GC 算法原因
年轻代复制算法对象存活率低,复制开销小
老年代标记-整理对象存活率高,复制开销大

P7 级别:现代 GC 算法的演进

CMS(Concurrent Mark Sweep)

CMS 是一种并发的标记-清除算法,目的是减少停顿时间:

graph TD
    A[初始标记<br/>Stop-The-World] --> B[并发标记<br/>用户线程运行]
    B --> C[重新标记<br/>Stop-The-World]
    C --> D[并发清除<br/>用户线程运行]

    style A fill:#ff6b6b
    style C fill:#ff6b6b

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 类型),而非物理上的固定区域。