垃圾回收

共 18 题
📑 题目列表 18 题
#
★★★

1. G1 GC region 设计(默认 2048 个 region)?

G1 GC 的 region 设计(默认 2048 个 region)是什么?它如何工作?

  • region 的划分与大小
  • 增量收集与可预测停顿
  • 年轻代/老年代在 region 上的布局

G1(Garbage First)把堆划分为固定大小的 region(默认最多 2048 个,每个 region 大小由堆大小决定,1MB-32MB,为 2 的幂次)。region 是 G1 实现增量收集的基础:年轻代、老年代、Humongous(大对象)都由 region 组成,但 region 不再固定归属某代,而是动态分配。G1 追踪各 region 的垃圾占比,优先回收垃圾最多的 region("Garbage First"),每次只收集一部分 region,从而把停顿控制在一个可预测的范围内(停顿目标,如 200ms)。通过 SATB 和 RSet 实现并发标记与跨 region 引用跟踪。

region 把"整堆"拆成"可增量处理的小单元",让 GC 可以分段收集、控制停顿。这是 G1 相比"整堆整理"的 CMS 的关键进步,也是其停顿可预测的基础。

#
★★★

2. Shenandoah GC 与 ZGC 在并发压缩上的设计差异?

Shenandoah GC 与 ZGC 在并发压缩上的设计差异是什么?

  • 并发整理的机制
  • Shenandoah 的转发指针
  • ZGC 的着色指针

两者都是面向大堆、低停顿的并发 GC,核心差异在"并发压缩/移动对象"的实现方式。Shenandoah 使用"转发指针"(forwarding pointer):对象移动时,原对象槽写入转发指针,通过额外的读屏障(load barrier)在访问时解析转发指针,把应用重定向到新位置,实现并发移动对象而不暂停。ZGC 使用"着色指针"(colored pointers):把指针的高位(如第 42 位以上的颜色位)编码对象状态(mark、relocate、remap 等),通过读屏障根据颜色决定是否重定位。Shenandoah 的转发指针在对象本身(需额外字段),ZGC 的着色指针在引用本身(需 reserved 位,因此影响堆大小上限)。

差异本质是"移动状态存在哪里":对象里(转发指针)vs 指针里(着色指针)。两者都靠读屏障实现并发移动,但机制与地址空间约束不同。

#
★★★

3. 为何 ZGC 在 TB 级堆上暂停时间仍 < 1ms?

为什么 ZGC 在 TB 级堆上暂停时间仍能保持在 1ms 以下?

  • 并发标记/转移/重映射
  • 着色指针与读屏障
  • 停顿与堆大小无关

ZGC 把 GC 的大部分工作(标记、转移、重映射)都设计为并发执行,与应用线程并行,只有极少数需要同步的步骤(如启动阶段的 root 扫描、STW 快照)才暂停,且这些暂停的时间与堆大小无关(只与线程数、root 数量相关)。通过着色指针(colored pointers)把对象状态编码进指针,配合读屏障(load barrier),应用线程在访问对象时能自动触发重定位,从而无需 STW 整理。因此即使堆达到 TB 级,暂停时间也基本恒定在 1ms 以下。

ZGC 的关键是"把与堆大小相关的 GC 工作全部并发化,只保留与堆大小无关的短停顿",从而让停顿与堆规模解耦。这是"低延迟"GC 的核心思路。

#
★★★

4. write barrier 在并发 GC 中维护三色不变性的角色?

write barrier(写屏障)在并发 GC 中维护三色不变性的角色是什么?

  • 三色标记(白/灰/黑)
  • 写屏障类型(SATB/增量更新)
  • 防止并发漏标

在并发标记(marking)中,对象被分为三色:白色(未访问)、灰色(已访问但子对象未处理完)、黑色(已访问且子对象已处理)。并发 GC 与应用线程交替执行时,应用可能修改对象引用,导致"黑色对象引用白色对象"的漏标,使得该白对象被误回收。write barrier(写屏障)在每次对象引用被写入时执行,用于维护三色不变性:SATB(Snapshot-At-The-Beginning)记录被覆盖的旧引用(保证初始快照不被破坏),增量更新(incremental update)记录新写入的引用(把目标标灰)。G1 用 SATB,CMS 用增量更新,ZGC/Shenandoah 用各自的屏障。

写屏障是"并发 GC 安全性的守护":它把被并发修改的引用记录下来,防止标记漏掉仍存活的对象。这是并发标记正确性的关键。

#
★★★

5. Go 三色标记(tri-color marking)算法?

Go 的三色标记(tri-color marking)算法是什么?

  • 三色定义
  • 标记与清扫
  • 混合写屏障

Go 使用三色标记-清扫(tri-color mark-sweep)GC:对象分为白(未被标记)、灰(标记中)、黑(已标记)。GC 从根对象出发,标记为灰,然后循环处理灰对象,把其引用的对象标灰、自身标黑,直到没有灰对象,剩下的白对象即为垃圾,被清扫回收。Go 并发标记使用写屏障(混合写屏障,插入写屏障 + 删除写屏障)保证并发下的正确性,标记与清扫与程序并发执行,减少停顿。Go GC 是并发标记-清扫,不整理对象(不移动),靠并发与分代(新版本引入)控制停顿。

三色标记把"标记"建模为"从灰到白的传播",是 GC 的基础算法。Go 通过混合写屏障实现并发标记,在不移动对象的前提下控制停顿,是"吞吐优先 + 低延迟兼顾"的典型。

#
★★★

6. HotSpot 中 card table 在 minor GC 跨代引用扫描中的作用?

HotSpot 中 card table 在 minor GC 跨代引用扫描中的作用是什么?

  • card table 的粒度
  • 跨代引用的记录
  • 加速 minor GC 根扫描

HotSpot 分代 GC 中,minor GC 只回收年轻代,但老年代对象可能引用年轻代对象,因此需要扫描老年代以找到这些跨代引用作为根。为加速,HotSpot 使用 card table(卡片表):把老年代划分为许多固定大小的卡(card,通常 512B),有一个对应的 byte 数组记录哪些卡包含指向年轻代的引用(dirty 卡)。minor GC 时只需扫描 dirty 的卡,而不必扫描整个老年代,从而大幅减少根扫描范围。写屏障在向老年代写入引用时会把对应卡标记为 dirty。注意:扫描的是"卡"而非单个对象,牺牲精度换取速度。

card table 是"按卡粒度记录跨代引用"的空间换时间优化:不用逐个对象扫描老年代,只需扫描被标记的卡,显著加速 minor GC。

#
★★★

7. GC roots 包含哪些对象?安全点(safepoint)如何让所有线程在可枚举根的位置停顿,JIT 编译与内联如何配合?

GC roots 包含哪些对象?安全点(safepoint)如何让所有线程在可枚举根的位置停顿?JIT 编译与内联如何配合?

  • GC roots 的内容
  • safepoint 机制
  • JIT 与栈映射(oops)

GC roots 是 GC 可达性分析的起点,包含:栈上局部变量/参数中的引用、活动寄存器中的引用、JNI 引用、静态字段、类加载器、线程对象等。为保证能枚举所有根,GC 需要所有线程停在安全点(safepoint):JIT 编译的代码在安全点位置插入检查点,线程运行到安全点时检查并挂起,同时在安全点记录栈上的引用位置(栈映射 oop map)。JIT 编译与内联配合:内联会改变栈帧布局,编译器必须在安全点生成精确的 oop map,记录每个位置是否为引用,使 GC 能准确遍历栈根。长期运行的方法(如循环)也会被插入安全点检查以保证及时停顿。

GC 根扫描的正确性依赖"安全点 + 栈映射":安全点让线程停在被枚举的已知位置,JIT 生成的 oop map 让 GC 知道栈上哪些槽是引用。这是"精确 GC(accurate GC)"在 JIT 下的实现基础。

#
★★

8. ZGC 的 colored pointers 与并发标记?

ZGC 的 colored pointers(着色指针)与并发标记是如何工作的?

  • 着色指针的位编码
  • 并发标记
  • 读屏障与重定位

ZGC 把指针的高位(64 位中的第 42 位以上)用于编码对象状态(称为 colored pointers),包括 mark bit、remap bit、relocate bit 等颜色位,低 42 位存放对象地址。并发标记时,GC 通过着色指针的 mark 位标记对象是否存活,标记过程与应用并发。访问对象时,读屏障(load barrier)检查指针的颜色位:若需要重定位(relocate),则通过屏障把访问重定向到新地址并更新指针。这样对象移动与标记都在并发完成,无需 STW 整理。着色指针要求堆大小受限于可用的地址位数(如 42 位 = 4TB)。

着色指针把 GC 状态"戴"在指针上,读屏障在访问时按颜色决策,从而把标记与重定位并发化。这是 ZGC 低延迟的核心,代价是限制堆大小上限。

#
★★

9. generational hypothesis(分代假说)与 minor/major GC 关系?

generational hypothesis(分代假说)是什么?它与 minor/major GC 的关系是什么?

  • 分代假说的内容
  • 年轻代/老年代划分
  • minor/major GC

分代假说(generational hypothesis)指出:绝大多数对象存活时间很短("朝生夕死"),只有少数对象存活很久。据此,GC 把堆划分为年轻代(young)和老年代(old):新对象放入年轻代,年轻代频繁用小规模的 minor GC(只回收年轻代)回收大部分短命对象,代价低、频率高;少数存活对象晋升到老年代,用低频的 major/full GC 回收。这样把 GC 的绝大多数工作集中在对象密集分配又密集死亡的年轻代,提高效率、降低停顿。

分代假说是"按存活特征分而治之":年轻代用高频率低成本回收,老年代用低频全面回收。minor/major GC 的划分正是这一假说的实践。

#
★★

10. mark-sweep、mark-compact、copying 三种 GC 算法的差异?

mark-sweep、mark-compact、copying 三种 GC 算法的差异是什么?

  • 三种算法的步骤
  • 碎片与停顿
  • 适用场景

mark-sweep(标记-清除):先标记存活对象,再清除未标记的垃圾对象,不移动对象,速度快但产生碎片、分配慢。mark-compact(标记-整理):标记后把存活对象压缩到堆的一端,消除碎片但需要移动对象、停顿更长。copying(复制):把堆分为 from/to 两块,把存活对象从 from 复制到 to,同时消除碎片且分配只需移动指针,但只利用一半空间。三者权衡:mark-sweep 快但碎片多,mark-compact 无碎片但慢,copying 高效无碎片但浪费空间。现代 GC 常组合(如新生代用 copying,老年代用 mark-compact)。

三种算法是"碎片 vs 停顿 vs 空间"的三角权衡:mark-sweep 省移动费空间,mark-compact 省空间费移动,copying 快而省碎片但浪费空间。分代 GC 按代选择不同算法。

#
★★

11. 为何 Java HotSpot 把堆划分为 young/old 区域?

为什么 Java HotSpot 把堆划分为 young/old 区域?

  • 分代假说的应用
  • 年轻代/老年代设计
  • 性能收益

HotSpot 采用分代收集,依据分代假说把堆划分为年轻代(young)和老年代(old):年轻代放新对象,多数对象很快死亡,用复制算法进行高频、低成本的 minor GC 回收;存活对象晋升到老年代,用低频的 mark-compact 等算法进行 major GC。这样做的收益:1)GC 集中在年轻代,避免每次扫描整个堆,降低停顿;2)年轻代用复制算法无碎片且分配快;3)老年代对象少且稳定,减少 GC 频率。这是分代假说在 HotSpot 中的落地。

young/old 划分是"按对象存活时间分层":年轻代高频低成本回收,老年代低频全面回收,优化了 GC 的吞吐与停顿。card table 等机制支撑跨代引用。

#
★★

12. CMS(Concurrent Mark Sweep)为何被 G1 取代?

CMS(Concurrent Mark Sweep)为什么被 G1 取代?

  • CMS 的碎片问题
  • CMS 的停顿与 CPU 开销
  • G1 的可预测停顿

CMS 是并发标记-清除收集器,目标是低停顿,但存在明显缺陷:1)mark-sweep 不整理,产生大量碎片,最终可能触发降级为 serial 的 Full GC,停顿反而变长;2)并发标记阶段的 CPU 开销大,且与"浮动垃圾"(并发期间产生的垃圾,需下次回收)导致空间不足时被迫 Full GC;3)停顿不可预测。G1 用 region + 增量收集 + 可预测停顿目标,并兼顾整理,解决了 CMS 的碎片与停顿不可控问题,因此 JDK 9 起 CMS 被废弃,以 G1 为默认。

CMS 被取代的根因是"不整理导致碎片、停顿不可预测"。G1 以 region 增量收集和可预测停顿目标,在低延迟与可控性上更优,是技术演进的结果。

#
★★

13. GC 的标记-清除/复制/标记-整理,三种算法如何对比?

GC 的标记-清除、复制、标记-整理三种算法如何对比?

  • 各算法步骤与特点
  • 碎片、停顿、空间
  • 分代中的选择

标记-清除(mark-sweep):标记存活对象后清除垃圾,不移动,速度快但碎片多、分配慢。复制(copying):将存活对象复制到新空间,分配只需指针移动、无碎片,但浪费一半空间。标记-整理(mark-compact):标记后把存活对象压缩,无碎片、空间利用率高,但移动对象导致停顿长。对比核心:碎片、停顿、空间利用率三者的权衡。典型选择:新生代对象多、存活少,用复制;老年代对象少、存活多,用标记-整理(或标记-清除),平衡碎片与停顿。

三种算法各有所长,分代 GC 按"对象存活率"选择:存活率低用复制(快),存活率高用标记-整理(省空间、少移动)。这是"算法适配对象特征"的体现。

#
★★

14. G1 的 SATB(Snapshot-At-The-Beginning)与 RSet(Remembered Set)分别解决什么问题,跨 region 引用如何被记录?

G1 的 SATB(Snapshot-At-The-Beginning)与 RSet(Remembered Set)分别解决什么问题?跨 region 引用如何被记录?

  • SATB 解决并发标记漏标
  • RSet 解决跨 region 引用扫描
  • 写屏障记录引用

G1 的 SATB(Snapshot-At-The-Beginning)解决并发标记的正确性问题:在标记开始时取一个快照,通过写屏障记录所有被覆盖的旧引用,保证标记期间即使应用修改引用,也不会漏掉标记开始时仍存活的对象(防止漏标)。RSet(Remembered Set)解决跨 region 引用问题:每个 region 维护一个 RSet,记录有哪些其他 region 引用本 region 的对象,这样收集某个 region 时只需扫描其 RSet 中的引用,无需扫描整个堆。跨 region 引用通过写屏障在写入时记录到目标 region 的 RSet。SATB 保证正确性,RSet 保证增量收集的效率。

两者是 G1 相辅相成的两大机制:SATB 保证并发标记不误回收存活对象,RSet 让增量收集只扫描相关 region。都依赖写屏障维护。

#
★★

15. 引用计数(如 Swift ARC、C++ shared_ptr)与追踪式 GC(Java/Go)在循环引用、停顿时间与吞吐上的本质差异?

引用计数(如 Swift ARC、C++ shared_ptr)与追踪式 GC(Java/Go)在循环引用、停顿时间与吞吐上的本质差异是什么?

  • 循环引用处理
  • 停顿时间
  • 吞吐

引用计数(ARC/shared_ptr):每次引用变化立即增减计数,计数归零即时释放,对象释放即时、可预测、无长停顿,但无法处理循环引用(需 Weak 打破),且每次赋值都有计数操作开销,在高频引用场景略增开销。追踪式 GC(Java/Go):从根可达性分析整个对象图,能自动处理循环引用(不可达即回收),但需要遍历对象图,可能产生停顿(虽然现代 GC 努力并发化),且回收是延迟的(GC 触发时才回收)。吞吐上,引用计数分配释放即时、无全局扫描,但计数操作频繁;追踪式 GC 有周期性扫描开销但可批处理。差异本质:即时 vs 延迟、按引用处理 vs 按图处理。

引用计数"局部、即时、难处理环",追踪式 GC"全局、延迟、能处理环"。选择取决于对延迟确定性、循环引用与吞吐的权衡。

#

16. Go GC 如何在并发下避免悬空指针?

Go GC 如何在并发下避免悬空指针?

  • 并发标记的安全性
  • 写屏障与三色
  • 栈的根扫描

Go GC 在并发标记时,通过写屏障(混合写屏障)维护三色不变性,防止并发修改引用导致漏标,从而保证不会回收仍被引用的对象,避免悬空指针。同时,Go 在标记栈根时,通过让线程在安全点停顿并扫描栈,确保根集合准确;对于栈上对象,Go 采用"栈扫描时是 STW 或存活/惰性"策略,确保栈上引用不被遗漏。由于 Go GC 是追踪式的(从根可达性判断),不会像手动释放那样产生悬空指针——只要对象被引用,就不会被回收。三色标记保证"黑色对象不会引用白色对象",从根可达的对象必然存活。

Go 避免悬空指针靠"追踪式 GC + 并发正确性":可达性分析保证存活对象不被回收,写屏障保证并发标记不破坏可达性。这是 GC 语言相对手动管理内存的安全优势。

#

17. 分代 GC 与三色标记,为什么新生代用复制、老年代用标记整理?

分代 GC 与三色标记是如何结合的?为什么新生代用复制、老年代用标记整理?

  • 分代与算法适配
  • 新生代复制
  • 老年代标记整理

分代 GC 依据分代假说,对不同代采用不同算法:新生代对象存活率低(多数短命),用复制(copying)算法,把存活对象复制到另一区域,分配只需移动指针、无碎片、高效,适合高死亡率的年轻代。老年代对象存活率高,用标记-整理(mark-compact)或标记-清除,因为复制代价高(存活对象多,复制开销大),标记-整理在消灭碎片的同时避免大量复制。三色标记(mark 阶段)是标记算法的实现,用于老年代的标记步骤。整体上,分代 GC 通过"按代选算法"结合三色标记,兼顾吞吐与碎片。

算法选择依据"存活率":存活率低用复制(快、无碎片),存活率高用标记整理(省空间、少移动)。三色标记是标记阶段的基础,分代是对算法的最优分配。

#

18. GC 的停顿,并发标记与读写屏障如何配合?

GC 的停顿与并发标记、读写屏障的关系是什么?

  • GC 停顿的来源
  • 并发标记
  • 读写屏障的作用

GC 停顿(stop-the-world)源于标记、整理、根扫描等需要与应用线程同步的步骤。为减少停顿,现代 GC 采用并发标记:标记阶段与应用线程并发执行,通过读写屏障(write barrier 维护三色、read barrier 处理对象移动)保证并发安全。并发标记让大部分标记工作不阻塞应用,读写屏障在访问/写入时做轻量同步,使 GC 只在必要的短停顿点(如根扫描、进入并发阶段)暂停。这样用屏障的微开销换取整体停顿的降低,实现低延迟。

并发 GC 的本质是"用读写屏障的轻量开销换取应用停顿的减少":屏障让标记/移动与应用并发,只保留必要的短停顿。这是低延迟 GC 的核心设计。