G1(Garbage First Collector)是如何进行垃圾回收的

G1 收集器的设计目标:替换掉 CMS

CMS的缺点参考:cms垃圾收集器是如何进行垃圾回收的

  1. 不牺牲系统的吞吐量;

  2. 与应用线程同时工作,几乎【注意措辞】不需要stop the world

    与CMS类似

  3. G1 基于复制算法,高效的整理剩余内存,不产生内存碎片

    CMS 使用的是 Mark-sweep 算法,自然会产生内存碎片,CMS只能在Full GC时,用stop the world整理内存碎片

    对比Parallel Scavenge(基于copying )、Parallel Old 收集器(基于 mark-compact-sweep),Parallel 会对整个区域做整理导致 gc 停顿会比较长,而 G1 只是特定地整理几个 region。

  4. GC使停顿更加可控

    对于CMS来说如果出现了Full GC时,则会对新生代和老年代的堆内存进行完整的整理,停顿时间就不可控了

    G1 并非一个实时的收集器,与 parallelScavenge 一样,对 gc 停顿时间的设置并不绝对生效,只是 G1 有较高的几率保证不超过设定的 gc 停顿时间。与之前的 gc 收集器对比,G1 会根据用户设定的 gc 停顿时间,智能评估哪几个 region 需要被回收可以满足用户的设定

  5. gc不要求额外的内存空间。

    CMS需要预留空间存储浮动垃圾

实现思路

传统垃圾收集器堆结构 Vs G1堆结构

传统垃圾收集器堆结构

在这里插入图片描述
G1 堆结构
在这里插入图片描述
G1 采取了不同的策略来解决并行、串行和CMS收集器的碎片、暂停时间不可控等问题,G1 将 整个堆分成相同大小的分区(Region)。

  • heap 被划分为一个个相等的不连续的内存区域(regions) ,每个 region 都有一个分代的角色: eden、 survivor、 old ,新生代和老年代不是物理隔离,而是一部分 Region(不需要连续)的集合。

    每个分区都可能是年轻代也可能是老年代,但是在同一时刻只能属于某个代。年轻代、幸存区、老年代这些概念还存在,成为逻辑上的概念,这样方便复用之前分代框架的逻辑。

    在物理上不需要连续,则带来了额外的好处。有的分区内垃圾对象特别多,有的分区内垃圾对象很少,G1会优先回收垃圾对象特别多的分区,这样可以花费较少的时间来回收这些分区的垃圾,这也就是 G1 名字的由来,即首先收集垃圾最多的分区。

  • 对每个角色的数量并没有强制的限定,也就是说对每种分代内存的大小,可以动态变化

  • 依然是在新生代满了的时候,对整个新生代进行回收-----------整个新生代中的对象,要么被回收、要么晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小

  • G1 最大的特点就是高效的执行回收,每次 GC 时所有新生代都会被扫描,但优先去执行那些大量对象可回收的区域(region)

  • G1 使用了 gc 停顿可预测的模型,来满足用户设定的 gc 停顿时间,根据用户设定的目标时间,G1 会自动地选择哪些 region 要清除,一次清除多少个 region。(后台根据各个 Region 回收获取的空间大小和回收所需时间的经验值,存放在一个优先列表中)

  • G1 从多个 region 中复制存活的对象,然后集中放入一个 region 中,同时整理、清除内存(copying 收集算法)

    G1还是一种带压缩的收集器,在回收老年代的分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩。

G1 GC的两种收集模式

Young GC 和 Mixed GC 是分代 G1 模式下选择 Cset 的两种子模式

G1的运行过程是这样的:会在 Young GC 和 Mixed GC 之间不断地切换运行,同时定期地做全局并发标记,在实在赶不上对象创建速度的情况下,使用 Full GC(这时候会回退到 Serial GC)。

  • Young GC:方式是选定所有年轻代里的 Region。通过控制年轻代的 Region 个数,即年轻代内存大小,来控制 Young GC 的时间开销。
  • Mixed GC:选定所有年轻代里的Region,外加根据全局并发标记(global concurrent marking)统计得出收集收益高(垃圾更多)的若干老年代 Region。在用户指定的开销目标范围内尽可能选择收益高的老年代 Region。
  • Mixed GC 不是 Full GC(G1 中没有 Full GC),它只能回收部分老年代的 Region,如果 Mixed GC 实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行 Mixed GC,就会使用 Serial Old GC (里面有 Full GC)来收集整个 GC heap。 所以本质上,G1 是不提供 Full GC 的

G1运行过程

1. G1 Young GC

Young GC 主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者 Survivor 空间。

在这种情况下Eden空间的数据移动到 Survivor空间中,如果 Survivor 空间不够,Eden空间的部分数据会直接晋升到老年代空间。 Survivor区的数据移动到新的 Survivor 区中,也有部分数据晋升到老年代空间中。

在回收之后所有之前属于 Eden 的区块全部变成空白,即不属于任何一个分区( Eden、Survivor、Old ),GC完成工作,应用线程继续执行。

G1 Young GC 过程

  • 阶段一:GC Root扫描

  • 阶段二:更新 RS

    处理 Dirty Card 队列,更新 RS

  • 阶段三:处理 RS

    检测从年轻代指向老年代的对象

  • 阶段四:对象拷贝

    拷贝存活的对象到 Survivor/old 区域

  • 阶段五:处理引用队列

    软引用、弱引用、虚引用处理

2. 全局并发标记(global concurrent marking)

global concurrent marking 的执行过程类似于 CMS,但是不同的是在 G1 GC 中,它主要是为 Mixed GC 提供标记服务的(即表示应该回收哪些老年代),并不是一次 GC 过程的一个必须环节。

下面为全局并发标记执行过程

  • 初始标记( initial mark, STW) :它标记了从 GC Root 开始直接可达的对象。并且修改 TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发执行时候(因为该阶段需要暂停用户线程),能在正确可用的 Region 中创建新对象。

  • 并发标记( Concurrent Marking) :这个阶段从GC Root 开始对堆中的对象进行可达性分析标记,标记线程与应用程序线程并发执行,并且收集各个 Region 的存活对象信息。

  • 重新标记( Remark, STW) :标记那些在并发标记阶段发生变化的对象,记录在线程 RSet Logs 中,同时将 RSet Logs 中的数据合并到 RSet 中,将被回收。(该阶段需要停顿但可以并行,同CMS)

  • 清理(Cleanup) :首先对各个 Region 中的回收价值和成本进行排序,根据用户期望的 GC 时间停顿时间来制定 回收计划,所以可能只清空一部分 Region。清除空 Region (没有存活对象的),加入到 free list。

第一阶段 initial mark 是共用了 Young GC 的暂停,这是因为他们可以复用 root scan 操作,所以可以说 global concurrent marking 是伴随 Young GC 而发生的;

第四阶段 Cleanup 只是回收了没有存活对象的 Region,所以它并不需要 STW。

mark的过程就是遍历堆标记live object的过程,采用的是三色标记算法,这三种颜色为white(表示还未访问到)、gray(访问到但是它用到的引用还没有完全扫描、black( 访问到而且其用到的引用已经完全扫描完)

整个三色标记算法就是从GC roots出发遍历堆heap,针对可达对象先标记white为gray,然后再标记gray为black;遍历完成之后所有可达对象都是black的,所有white都是可以回收的。

3. 混合模式 mixed gc

G1到现在可以知道哪些老的分区可回收垃圾最多。当全局并发标记完成后,在某个时刻(参考下面什么时候发生Mixed GC),就开始了Mixed GC。

这些垃圾回收被称作“混合式”,是因为他们不仅仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。混合式GC也是采用的复制清理策略,当GC完成后,会重新释放空间。

注意:在 G1 GC 中, Global concurrent Marking 主要是为 Mixed GC 提供标记服务的,并不是一次 GC 过程中的一个必须环节。

什么时候发生 Mixed GC?

由一些参数控制,另外也控制着哪些老年代 Region 会被选入 CSet (收集集合),下面是一部分的参数

  • G1HeapWastePercent:在 global concurrent marking 结束之后,我们可以知道 old gen regions 中有多少空间要被回收,在每次 YGC 之后和再次发生 Mixed GC 之前(YGC 和 Mixed GC 之间是交替进行,不是一次一次交替,可能是多次对一次的),会检查垃圾占比是否达到此参数,只有达到了,下次才会发生 Mixed GC;
  • G1MixedGCLiveThresholdPercent:old generation region 中的存活对象的占比,只有在此参数之下,才会被选入CSet
  • G1MixedGCCountTarget:一 次 global concurrent marking 之后,最多执行 Mixed GC 的次数
  • G1OldCSetRegionThresholdPercent:一次 Mixed GC 中能被选入 CSet 的最多 old generation region 数量

4. Full GC

一 般是G1出现问题时发生,本质上不属于G1,G1进行的回退策略(回退为:Serial Old GC)

当 MixedGC 赶不上对象产生的速度的时候就退化成 FullGC,这一点是需要重点调优的地方。

G1总结

G1收集器特点

  1. 并行与并发:使用多个 CPU 来缩短 STW 停顿的时间;同时可以并发操作;
  2. 分代收集:使用不同方式处理不同代对象;
  3. 空间整合:整体基于标记-整理算法,局部(两个 Region 之间)基于复制算法;即**没有内存碎片
  4. 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒。

它满足短时间 gc 停顿的同时达到一个较高的吞吐量,以上可以看到G1在吞吐量和响应能力上都进行了兼顾。

G1 的适合场景

  • 服务端多核 CPU、JVM 内存占用较大的应用
  • 应用在运行过程中会产生大量内存碎片、需要经常压缩空间
  • 想要更可控、可预期的 GC 停顿周期:防止高并发下应用的雪崩现象

G1最佳实践

  • 不要设置新生代和老年代的大小
    • G1 收集器在运行的时候会调整新生代和老年代 的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标。
    • 设置了新生代大小相当于放弃了 G1 为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给 G1 自已去分配各个代的大小即可。
  • 不断调优暂停时间指标
    • 通过 -XX:MaxGCPauseMillis=x 可以设置启动应用程序暂停的时间,G1 在运行的时候会根据这个参数选择 CSet 来满足响应时间的设置。一般情况下这个值设置到 100ms 或者 200ms 都是可以的(不同情况下会不一样),但如果设置成 50ms 就不太合理。暂停时间设置的太短,就会导致出现 G1 跟不上垃圾产生的速度,最终退化成 Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。
  • 关注Evacuation Failure
    • Evacuation(表示 copy) Failure 类似于 CMS 里面的晋升失败,堆空间的垃圾太多导致无法完成 Region之间的拷贝,于是不得不退化成 Full GC 来做一次全局范围内的垃圾收集

代码验证及G1 gc日志解析

程序代码为:

VM 参数为:-verbose:gc -Xms10m -Xmx10m -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:MaxGCPauseMillis=200m

其中:* -XX:+UseG1GC 表示指定垃圾收集器使用G1;-XX:MaxGCPauseMillis=200m 表示设置垃圾收集最大停顿时间

package com.gjxaiou.gc.g1;

/**
 * @Author GJXAIOU
 * @Date 2019/12/20 20:59
 */
public class G1LogAnalysis {
    public static void main(String[] args) {
        int size = 1024 * 1024;
        byte[] myAlloc1 = new byte[size];
        byte[] myAlloc2 = new byte[size];
        byte[] myAlloc3 = new byte[size];
        byte[] myAlloc4 = new byte[size];
        System.out.println("hello world");
    }
}

日志结果为:

// Humogous【说明分配的对象超过了region大小的50%】
// initial-mark 共用了 Young GC 的暂停
2019-12-20T21:02:10.163+0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0015901 secs]
// GC Workers【GC工作线程数】
[Parallel Time: 0.8 ms, GC Workers: 10]
// 【几个垃圾收集工作的相关信息统计】
	 [GC Worker Start (ms): Min: 90.3, Avg: 90.4, Max: 90.4, Diff: 0.1]
// 【下面的几个步骤为YOUNG GC的固定执行步骤】
// 阶段1:根扫描-- 静态和本地对象被扫描
	 [Ext Root Scanning (ms): Min: 0.1, Avg: 0.2, Max: 0.3, Diff: 0.1, Sum: 2.1]
// 阶段2:更新RS -- 处理dirty card队列更新RS
	 [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
	 	[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
// 阶段3:处理RS -- 检测从年轻代指向老年代的对象
	 [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
	 [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
// 阶段4:对象拷贝 -- 拷贝存活的对象到survivor/old区域
// 阶段5:处理引用队列 -- 软引用,弱引用,虚引用处理
      [Object Copy (ms): Min: 0.4, Avg: 0.4, Max: 0.5, Diff: 0.1, Sum: 4.4]
	  [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
	      [Termination Attempts: Min: 1, Avg: 5.4, Max: 8, Diff: 7, Sum: 54]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]
      [GC Worker Total (ms): Min: 0.7, Avg: 0.7, Max: 0.7, Diff: 0.1, Sum: 6.9]
      [GC Worker End (ms): Min: 91.1, Avg: 91.1, Max: 91.1, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
// 【 清除 cardTable所花费时间】
   [Clear CT: 0.1 ms]
   [Other: 0.7 ms]
// choose CSet
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
// free CSet
      [Free CSet: 0.0 ms]
// G1 Young GC finish
   [Eden: 2048.0K(6144.0K)->0.0B(2048.0K) Survivors: 0.0B->1024.0K Heap: 3725.2K(10.0M)->2836.0K(10.0M)]
 [Times: user=0.01 sys=0.00, real=0.00 secs] 

// 这里应该是global concurrent marking
2019-12-20T21:02:10.165+0800: [GC concurrent-root-region-scan-start]
2019-12-20T21:02:10.165+0800: [GC pause (G1 Humongous Allocation) (young)2019-12-20T21:02:10.165+0800: [GC concurrent-root-region-scan-end, 0.0006999 secs]

// GC concurrent-mark-start
2019-12-20T21:02:10.165+0800: [GC concurrent-mark-start]
, 0.0013416 secs]
   [Root Region Scan Waiting: 0.3 ms]
   [Parallel Time: 0.5 ms, GC Workers: 10]
      [GC Worker Start (ms): Min: 92.5, Avg: 92.6, Max: 92.6, Diff: 0.1]
      [Ext Root Scanning (ms): Min: 0.1, Avg: 0.1, Max: 0.2, Diff: 0.1, Sum: 1.0]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [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]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.3, Avg: 0.3, Max: 0.3, Diff: 0.0, Sum: 3.0]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Termination Attempts: Min: 1, Avg: 4.6, Max: 8, Diff: 7, Sum: 46]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [GC Worker Total (ms): Min: 0.4, Avg: 0.4, Max: 0.5, Diff: 0.1, Sum: 4.1]
      [GC Worker End (ms): Min: 93.0, Avg: 93.0, Max: 93.0, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.2 ms]
   [Other: 0.3 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.2 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]

// 【新生代清理后】
   [Eden: 1024.0K(2048.0K)->0.0B(1024.0K) Survivors: 1024.0K->1024.0K Heap: 3901.0K(10.0M)->4120.5K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 

// GC concurrent-mark-end
2019-12-20T21:02:10.166+0800: [GC concurrent-mark-end, 0.0012143 secs]

// 需要执行Full GC
2019-12-20T21:02:10.167+0800: [Full GC (Allocation Failure)  4120K->3676K(10M), 0.0020786 secs]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 1024.0K->0.0B Heap: 4120.5K(10.0M)->3676.9K(10.0M)], [Metaspace: 3091K->3091K(1056768K)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 

// GC remark
2019-12-20T21:02:10.169+0800: [GC remark, 0.0000082 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 

// concurrent-mark-abort
2019-12-20T21:02:10.169+0800: [GC concurrent-mark-abort]
hello world

// 堆区一共10M,执行了一次G1 Young GC,看起来最后都存储到了Old区
Heap
 garbage-first heap   total 10240K, used 4700K [0x00000000ff600000, 0x00000000ff700050, 0x0000000100000000)
// 【说明region默认大小】 
  region size 1024K, 1 young (1024K), 0 survivors (0K)
 Metaspace       used 3229K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 350K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

G1应用

在中间件中,适合于服务端多核 CPU、JVM 内存占用较大的应用
rocketmq的broker,使用的是G1 GC

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值