G1
简单总结了G1和其常用参数,并分析了的G1日志
G1(Garbage First)是 JDK 7 引入,并在 JDK 9 默认启用的服务端垃圾回收器。它的核心理念是将整个 Java 堆划分为多个大小相等的 Region,打破了传统“新生代 / 老年代”的物理分区模式,转而以 Region 为基本单位进行垃圾管理与回收。
- 整堆收集:G1 是一个真正的 整堆并行压缩收集器,新生代和老年代都可以并行回收。
- 并发标记:采用 三色标记法 + SATB + 写屏障机制,保证并发标记期间的准确性。
- 可预测的停顿时间:用户可设置
-XX:MaxGCPauseMillis
控制最大停顿时间,G1 会在这个目标下选择哪些 Region 进入回收集(CSet)。
常用参数设置
- -XX:+UseG1GC
- -XX:G1HeapRegionSize=2M:一个Region的大小
- -XX:MaxGCPauseMillis=80:允许收集停顿的最大时常(毫秒)
- -XX:InitiatingHeapOccupancyPercent=45 :老年代占用到 45% 时触发并发标记周期(默认45)
- 不要再设置-xmn和-XX:NewRatio
Region划分
- 整个堆被分成多个 Region(默认 2048 个),每个 Region 的大小为 1MB ~ 32MB,并且必须是 2 的幂次方。
- 每个 Region 会被动态标记为不同用途:
E
:Eden 区(新生代分配对象)S
:Survivor 区(新生代存活对象)O
:Old 区(长生命周期对象)H
:Humongous 区(大对象,直接分配在老年代)
大对象(如数组、长字符串等)若超过一个 Region 一半大小,会被当作 Humongous 对象,分配连续的多个 Region。由于这些对象移动成本高,G1 默认不会移动 Humongous 对象,而是直接将它们标记为老年代区域。
Remembered Set(RSet)
由于 G1 会独立地对某些 Region 进行回收,它必须知道老年代是否引用了某个新生代对象。这正是 Remembered Set(记忆集) 的作用:
- 每个 Region 都维护了一个对应的 RSet,记录有哪些其它 Region 的对象引用了自己。
- 在回收某个 Region 时,G1 只需要扫描这个 RSet,而不必全堆扫描,大幅降低了跨代引用处理的成本。
简单说:RSet 让分区式回收变得可能而高效。
Card Table(卡表)
RSet 的实现依赖于 Card Table + 写屏障:
- Java 堆被进一步划分为更小的单位:Card,默认每个 Card 是 512 字节。
- JVM 在写引用字段时会触发 写屏障(Write Barrier),将对应 Card 标记为 dirty,并记录引用变更。
- 在 GC 时,这些 dirty Card 会被用于更新 RSet,确保引用信息完整。
卡表是写屏障的基础,RSet 是分区引用追踪的核心,三者协同构成 G1 的并发收集体系。
日志解析
2020-11-23T11:40:46.167+0800: 1.503: [GC pause (G1 Evacuation Pause) (young), 0.0048336 secs]
// 下面的Min,Avg,Max,Diff,Sum分别表示GC线程最小启动或耗时时间(后面的也是),平均,最大,最大差值,和总耗时,单位都为ms
[Parallel Time: 3.1 ms, GC Workers: 6] // 本次YGC共6个GC线程,总耗时3.1ms
[GC Worker Start (ms): Min: 1503.0, Avg: 1504.5, Max: 1506.1, Diff: 3.0] // 本次GC线程启动(相对于JVM的启动)
[Ext Root Scanning (ms): Min: 0.0, Avg: 0.2, Max: 0.7, Diff: 0.7, Sum: 1.1] // 本次GC线程的GC Roots扫描时间
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] // 更新Remember Sets 的耗时统计信息(记忆集一般使用来解决跨Region的引用)
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] // 每个Region都会有一个RSet,RSet又包含指向这个Region的Cards引用,这个阶段就是扫描RSet中的Cards,从而分辨出Eden哪些对象被老年代引用,从而这些不会被GC
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.2, Diff: 0.2, Sum: 0.2] // 扫描代码中的root节点(局部变量)
[Object Copy (ms): Min: 0.0, Avg: 1.3, Max: 2.7, Diff: 2.7, Sum: 7.6] // 对象copy,将存活的对象copy到目标Region中
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.3]
[Termination Attempts: Min: 1, Avg: 1.2, Max: 2, Diff: 1, Sum: 7]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2] // GC线程完成其他任务的时间
[GC Worker Total (ms): Min: 0.0, Avg: 1.6, Max: 3.1, Diff: 3.1, Sum: 9.4] // GC线程整个生命周期总计消耗时间
[GC Worker End (ms): Min: 1506.1, Avg: 1506.1, Max: 1506.1, Diff: 0.0] // GC线程完成任务的停止时间(相对于JVM)
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms] // 清理Card Table(卡表)
[Other: 1.6 ms]
[Choose CSet: 0.0 ms] // 选择要回收的Region放入CSet(会根据停顿时间来决定)
[Ref Proc: 1.4 ms] // 处理引用对象耗时时间(Weak、Soft、Phantom、JNI等等)
[Ref Enq: 0.0 ms] // 遍历所有引用,将不能回收的放入pending列表
[Redirty Cards: 0.0 ms] // 重置card为dirty
// 大型对象的回收
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms] // 释放CSet中Region占用的内存空间所耗时间
[Eden: 51.0M(51.0M)->0.0B(46.0M) Survivors: 0.0B->5120.0K Heap: 51.0M(1024.0M)->4815.7K(1024.0M)]
[Times: user=0.05 sys=0.00, real=0.01 secs]
总结
G1 不再物理区分年轻代和老年代,转而统一使用多个 Region 管理整个堆。
支持 并发标记 + 并发回收 + 可预测停顿,是整堆压缩收集器。
使用 Remembered Set + Card Table + 写屏障 高效维护跨代引用关系。
避免 Full GC 的目标是:通过周期性并发标记、预测性选择 CSet 来进行碎片整理。