G1 Region 与 Remembered Set

候选人小庞在面试阿里 P7 时,面试官问道:

"G1 的 Remembered Set 是什么?它是怎么工作的?"

小庞说:"Remembered Set 记录 Region 之间的引用关系..."面试官追问:"Remembered Set 的内存占用大吗?"

小庞答不上来...

一、核心问题:Remembered Set 🔴

1.1 问题拆解

第一层:作用(做什么?)
  "G1 的 Remembered Set 是什么?"
  考察点:跨 Region 引用追踪

第二层:实现方式(怎么记录?)
  "Remembered Set 有哪几种实现?"
  考察点:PointIn vs PointOut

第三层:内存开销(有多大?)
  "Remembered Set 的内存占用是多少?"
  考察点:写屏障更新

1.2 标准回答

P5 级别:Remembered Set 的作用

为什么需要 Remembered Set?

在 G1 中,GC 时需要知道哪些 Region 引用了当前 Region,以避免全堆扫描。

Remembered Set 记录了指向当前 Region 的引用来自哪些其他 Region

graph TD
    subgraph G1 堆
        R1[Region 1<br/>Old]
        R2[Region 2<br/>Young]
        R3[Region 3<br/>Old]
    end

    R1 -->|引用| R2
    R3 -->|引用| R2

    subgraph Remembered Set of R2
        RS[R1, R3]
    end

P6 级别:实现方式

PointIn vs PointOut

实现记录方式优缺点
PointIn(G1 使用)记录"哪些 Region 引用了我"回收时查找方便,但更新代价高
PointOut记录"我引用了哪些 Region"更新代价低,但回收时查找麻烦

PointIn 实现(当前 Region 的 RS)

每个 Region 维护一个 Remembered Set,记录指向该 Region 的其他 Region。

P7 级别:内存开销

Remembered Set 的内存开销

Remembered Set 的内存占用与跨 Region 引用数量成正比:

  • 理论占用:堆的 10%~20%(高引用变化率场景下可能更高)
  • G1 的 Remembered Set 使用稀疏表存储(每个 Region 一个小数组)

写屏障的更新代价

每次对象引用赋值都会触发写屏障,更新相关 Region 的 Remembered Set。

【面试官心理】 这道题我能问到 P7 级别,是因为 Remembered Set 的内存开销是 G1 在大内存堆上的主要瓶颈。

1.4 追问升级

追问:ZGC 的 NMT(Native Memory Tracking)可以查看 Remembered Set 吗?

ZGC 使用 Colored Pointers 代替 Remembered Set,不需要维护 Region 间的引用关系。NMT 可以查看 ZGC 自身的内存使用,但不包含 Remembered Set。

二、生产调优 🟡

2.1 减少 Remembered Set 更新

# G1HeapRegionSize 越大,Region 数量越少,Remembered Set 开销越小
-XX:G1HeapRegionSize=16m  # 大 Region 减少 RS 开销
💡

面试加分点:能说出"JDK 14 实现了 G1 的 Region Flipping 优化,减少了 Remembered Set 的维护开销",说明他关注了 JDK 新特性。

⚠️

面试陷阱:被问到"Remembered Set 在 GC 完成后会被清理吗",很多人会说"不会"。准确答案是:Remembered Set 在每次 GC 完成后会被清理,因为每次 GC 后 Region 的引用关系可能发生变化,需要重新记录。