连载中 9/20

G1:把堆切成 Region 之后发生了什么

2026-09-15 · 6 阅读 · 0 评论 · 0 赞

从分代到分区

G1 上来就动了个大手术:不再物理切新生代、老年代两块大田,而是把堆划成 2048 个左右的Region(每个 1~32M,2 的幂)。每个 Region 动态扮演角色——这一会儿是 Eden,下一轮 GC 后变成 Survivor,再过段时间变成 Old。逻辑分代还在,物理边界消失。还有个特殊角色 Humongous:超过 Region 一半的大对象直接进这种 Region,多个连续 Humongous Region 拼起来装超大对象。

可预测的停顿模型

分区化给了 G1 一个前无古人的能力:回收不再是全有或全无。用户设定 -XX:MaxGCPauseMillis=200,G1 就按"本次最多停 200ms 能收多少 Region"来挑活干——优先收垃圾占比最高的 Region,这就是名字 Garbage First 的由来。停顿时间从默认值变成了显式契约,代价是吞吐让路:追求短停顿,单次收得就少,GC 就更频繁。

RSet:跨 Region 引用的账本

分区化带来新难题:只回收几个 Region,别的 Region 可能引用它们,不扫全堆怎么保证不漏活对象?答案是每个 Region 维护一份 RSet(Remembered Set),记录"谁引用了我"——点对点粒度比分代卡表更细,换来的是"回收任意 Region 集合"的自由。代价也不小:RSet 能吃掉堆的 10%~20%,这是 G1 的隐形成本。

回收的三种形态

// Young GC:Eden 满了触发,复制存活对象到 Survivor/Old
//
// 并发标记周期:老年代占比达到 IHOP(默认 45%)后启动
//   初始标记(STW) → 并发标记 → 最终标记(STW) → 清理(STW)
//
// Mixed GC:标记周期结束后连着几轮"混合回收"——
//   新生代 + 部分垃圾最多的老年代 Region 一起收
//   存活对象复制走,原 Region 整块回收 → 天然无碎片
//
// Full GC:以上都来不及时的降级方案,单线程整理整堆
//   G1 的 Full GC 是要极力避免的事故信号

注意 G1 的回收全是复制/整理式的——存活对象搬到新 Region,旧的整块清空。CMS 的碎片问题在这里从机制上消失了,代价是"搬家"需要停顿窗口,G1 把它拆碎成一次 200ms 以内的小窗口。

参数与选型

日常只需记三个参数:-XX:+UseG1GC(JDK 9+ 已是默认)、MaxGCPauseMillis(别设太狠,20ms 的目标会让 GC 频繁到伤吞吐)、InitiatingHeapOccupancyPercent(默认 45%,标记周期启动阈值)。选型经验:堆 6G 以上、需要可控停顿的场景选 G1;小内存场景 CMS/Parallel 反而更省心。停顿还想再往下压一个数量级?下一篇的主角 ZGC,把"亚毫秒停顿"带进了现实。

503

10 年全栈工程师 · 503咖啡馆主理人

#G1收集器#Region#RSet#MixedGC#停顿模型

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 1 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 12 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞