G1 深入:Region、Remembered Set、三色标记与“可预测停顿”

你如果在线上遇到过这类问题:

  • 接口 RT 偶尔抖一下,P99/P999 很难稳
  • Full GC 偶发一次,停顿几秒甚至十几秒
  • 堆不小,但又不敢把 STW 拉太长

那你问"为什么选 G1",本质是在问:

  • 我能不能让 GC 停顿更可控?
  • 我能不能用更小粒度回收,而不是动不动整代回收?

G1 之所以重要,是因为它把"回收粒度"和"停顿目标"变成了可解释、可观测、可调的工程问题。

这篇按一条主线把 G1 串起来:

  • Region:把堆切碎,回收粒度变小
  • RSet:解决跨 Region 引用,保证回收正确性
  • 并发标记(三色标记 + SATB + 写屏障):让标记尽量不阻塞业务
  • 停顿预测:按收益/成本挑回收集合,让停顿落在目标内

1. Region:为什么要把堆切碎

传统分代是"连续的大块空间",而 G1 用 Region 化的意义是:

  • 回收不再只能按"整代"进行
  • 可以挑选收益高的 Region 回收
  • 为可预测停顿提供基础

你可以把 Region 理解成:

  • 堆的最小管理单元

一个非常关键的工程意义:

  • G1 不是只能按"整代"回收,它可以按"哪些 Region 值得回收"来回收
  • 这为"可预测停顿"提供了基础

2. 跨 Region 引用:为什么一定需要 RSet(以及它的代价)

Region 化后会出现一个问题:

  • A Region 里的对象引用了 B Region 里的对象

当你只回收 B Region 时,你必须知道:

  • 是否有别的 Region 在引用 B Region 里的对象

否则会误回收。

这就是 Remembered Set(RSet)的作用:

  • 记录"哪些 Region 引用了我"

工程上你要知道的代价:

  • RSet 会占用额外内存(一定会吃掉一部分堆外/堆内元数据开销,依实现而定)
  • 维护 RSet 需要写屏障开销(每次写引用,都可能要更新相关记录)

为了把 RSet 的维护做得更可控,G1 通常还会配合类似"卡表(Card Table)"这样的粗粒度记账方式:

  • 把内存划成更小的卡片(card)
  • 写屏障把"某个区域的某张卡被写过"记录下来
  • 后续回收时再基于这些记录去更新/扫描

3. 并发标记:三色标记 + SATB + 写屏障(你要理解的不是名词,而是矛盾)

三色标记是一个思维模型:

  • 白色:未访问(可能是垃圾)
  • 灰色:已发现但子引用未完全扫描
  • 黑色:已扫描完(认为存活)

并发标记时,难点在于:

  • 标记线程在遍历对象图的同时,业务线程仍在修改引用关系

这就是为什么 G1(以及很多并发收集器)会引入:

  • 写屏障(Write Barrier):在"写引用"发生时插入一小段逻辑,维护并发标记需要的辅助信息
  • SATB(Snapshot-At-The-Beginning):以"标记开始时的对象图快照"作为基准,处理并发修改带来的不一致

你不用背到源码级,但要会解释清楚:

  • 并发标记要解决"对象图在变化"的一致性问题,否则会出现误标/漏标
  • 写屏障与 SATB 是为了在可控成本下,把并发标记变成"结果正确"

4. G1 的回收主流程:Young GC、并发标记、Mixed GC

只说"G1 把堆切成 Region"是不够的,你需要能把主流程讲清楚。

你可以按这个顺序理解:

  • Young GC(年轻代回收):优先回收年轻代 Region,停顿相对短
  • 并发标记(Concurrent Mark):标记存活对象,为后续 Mixed 回收提供依据
  • Mixed GC(混合回收):把一部分"收益高"的老年代 Region 加入回收集合

这就是为什么很多在线服务会选择 G1:

  • 年轻代回收保持高频低成本
  • 老年代回收不一定要等到非常满才来一次很重的 Full GC,而是能被 Mixed 分摊

5. 停顿预测:G1 的"可控"从哪里来

G1 的目标是:

  • 尽量满足你设定的停顿目标(比如 200ms)

它的做法大致是:

  • 统计每个 Region 的回收收益(可回收垃圾比例)与成本(预计回收耗时)
  • 在一次回收中选择一批 Region(称为回收集合)
  • 让这批 Region 的回收成本尽量落在停顿预算内

因此你看到的工程现象:

  • G1 的停顿相对更"可预测"(但不是绝对保证)
  • 代价是更复杂的管理结构(Region/RSet/写屏障)

6. 工程上怎么观测与排障:先会看现象,再谈"调参"

G1 调参之前,你至少要能回答:

  • 现在卡顿来自 Young GC 还是 Mixed GC?
  • 停顿时间主要花在"扫描引用"还是"对象搬迁(Evacuation)"?
  • 是不是存在晋升失败、老年代增长过快、或回收跟不上分配?

6.1 你可以先用这些命令做低侵入确认

  • 查看堆结构:jcmd <pid> GC.heap_info
  • 查看线程是否 STW 后堆栈堆在一起:jcmd <pid> Thread.print(卡顿时抓多次对比)
  • 查看对象大户:jcmd <pid> GC.class_histogram

6.2 如果有 GC 日志,你重点盯这些关键词

  • Pause Young (Normal):年轻代回收
  • Pause Young (Mixed):混合回收(年轻代 + 部分老年代 Region)
  • Evacuation Failure / to-space exhausted:搬迁空间不够(危险信号)

你不需要一开始就把每个字段都读懂,但要形成"把一次停顿拆成模块"的习惯:

  • 哪一段耗时最大
  • 是不是某类对象导致搬迁成本过高

7. 常见调参思路(只讲工程上常用、且容易解释清楚的)

这部分我建议你把"目标 -> 动作 -> 代价"讲出来,而不是背参数。

7.1 目标:停顿更可控

  • 动作:设置一个合理的停顿目标(例如 -XX:MaxGCPauseMillis=200
  • 代价:为了满足停顿目标,G1 可能更频繁地回收,吞吐会有损失

7.2 目标:减少 Mixed/Old 压力

  • 动作:关注晋升是否过快、对象是否真的长期存活(缓存/集合/大对象)
  • 代价:本质往往是业务对象生命周期问题,不是单靠参数能解决

8. 总结:你讲得清楚 G1,就讲得清楚"为什么要它"


  • Region:把回收粒度切小,支持按收益选择回收集合
  • RSet:解决跨 Region 引用,保证回收正确性(但有内存与写屏障代价)
  • 并发标记:三色标记 + SATB + 写屏障,是为了解决"对象图在变化"的一致性
  • 回收主流程:Young GC -> 并发标记 -> Mixed GC,尽量避免一次巨大的 Full GC
  • 停顿预测:基于收益/成本模型选择回收集合,让停顿更接近目标
相关推荐
3DVisionary7 分钟前
蓝光三维扫描:医疗制造的精度焦虑怎么解
人工智能·算法·制造·蓝光三维扫描·医疗制造·三维检测·义齿检测
好评笔记16 分钟前
机器学习面试八股——常用损失函数
人工智能·深度学习·算法·机器学习·校招
weixin_4684668517 分钟前
全局与局部注意力机制新手实战指南
人工智能·python·深度学习·算法·自然语言处理·transformer·注意力机制
_日拱一卒40 分钟前
LeetCode:994腐烂的橘子
java·数据结构·算法·leetcode·深度优先
隔窗听雨眠1 小时前
Nginx网关响应慢排查手记
java·服务器·nginx
丷丩1 小时前
Postgresql基础实践教程(十一)各种Join
数据库·postgresql·join
星夜夏空991 小时前
FreeRTOS学习(4)——内存映射
数据库·学习·mongodb
珂朵莉MM1 小时前
第七届全球校园人工智能算法精英大赛-算法巅峰赛产业命题赛第3赛季优化题--束搜索
人工智能·算法
智慧物业老杨1 小时前
智慧物业合同周期管理系统:从风险预警到智能交接的全流程数智化落地方案
java·人工智能·python
源码宝2 小时前
MES系统源码:Java8 + SpringBoot2.7 + MySQL8 + Redis,后端源码清爽易扩展
java·后端·源码·springboot·mes系统·源码二开·mes源码