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 来进行碎片整理。