前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >为什么G1 GC从JDK 9之后成为默认的垃圾回收器?

为什么G1 GC从JDK 9之后成为默认的垃圾回收器?

作者头像
Java团长
发布2019-05-17 16:03:01
5.3K0
发布2019-05-17 16:03:01
举报
从JDK 9开始G1替代并行垃圾回收器成为JVM中默认的垃圾回收器(具体可见JEP提案248,地址为https://openjdk.java.net/jeps/248),并且官方将CMS标记为丢弃(具体可见JEP提案291,地址为https://openjdk.java.net/jeps/291)。G1能够脱颖而出,成为最大的赢家,其最主要的原因就是在过去几年间,众多使用者使用G1之后发现G1的性能表现的非常优秀。

目前JVM提供的正式垃圾回收器有并行、串行、CMS和G1。这四种垃圾回收器都是基于分代内存管理。分代管理就是把内存划分成不同的区域进行管理,其思想来源是:有些对象存活的时间短,有些对象存活的时间长,把存活时间短的对象放在一个区域管理,把存活时间长的对象放在另一个区域管理,那么可以为两个不同的区域选择不同的算法,加快垃圾回收的效率。我们假定内存被划分成2个代,新生代和老生代。把容易死亡的对象放在新生代,通常采用复制算法回收,把预期存活时间较长的对象放在老生代,通常采用标记清除算法。

虽然并行、串行、CMS和G1都是采用了分代的内存管理,垃圾回收时采用的算法也都是复制算法或者标记清除算法。但是每一种垃圾回收器的实现并不相同。其主要的区别可以总结为:

  • 内存管理方式不同。
  • 回收算法实现不同。

内存管理

在并行、串行以及CMS中针对堆空间的管理方式都是连续的。如下图所示:

连续的内存将导致垃圾回收时收集时间过长,停顿时间不可控。在某些场景中因垃圾回收导致应用程序暂停超过数分钟、数秒等屡见不鲜。

所以G1将堆拆成一系列的分区(Heap Region),这样在一个时间段内,大部分的垃圾回收操作就只是针对一部分分区执行,而不是整个堆或整个(老年)代,从而满足在指定的停顿时间内完成垃圾回收的动作。G1内存分区如下图所示:

在G1新生代就是一系列的内存分区,这意味着不用再要求新生代是一个连续的内存块。类似地,老生代也是由一系列的分区组成。在JVM运行时,从内存管理角度不需要预先设置分区是老生代分区还是新生代分区,而是在内存分配时决定,当新生代需要空间则分区被加入到新生代中,当老生代需要内存空间则分区被加入到老生代中。事实上,G1通常的运行状态是:映射G1分区的虚拟内存随着时间的推移在不同的代之间切换。例如一个G1分区最初被指定为新生代,经过一次新生代的回收之后,整个新生代分区都被划入到待使用的分区中,那它就可以作为新生代分区使用,也可以作为老生代分区使用。很可能在完成一个新生代回收之后,一个新生代的分区在未来的某个时刻被用于老生代分区。同样地,在一个老生代分区完成回收之后,它就成为待使用分区,在未来某个时候作为一个新生代分区来使用。

G1新生代的回收方式是并行回收,采用复制算法。与其他JVM垃圾回收器一样,一旦发生一次新生代回收,整个新生代都会被回收。这也就是我们常说的新生代回收(Young GC,简称为YGC)。但是G1和其他垃圾回收器不同的地方在于:一、G1会根据预测时间动态的改变新生代的大小(G1中预测时间是根据运行垃圾回收的历史数据通过数学建模预测得到,所用的数学模型是衰减平均);二、G1老生代的垃圾回收方式与其他JVM垃圾回收器对老生代处理有着极大的不同。G1老生代的回收不会为了释放老生代的空间对整个老生代做回收。相反,在任意时刻只有一部分老生代分区会被回收,并且,这部分老生代分区将在下一次增量回收时与所有的新生代分区一起被回收。这就是我们所说的混合回收(Mixed GC),在选择老生代分区的时候,优先考虑垃圾多的分区。

回收算法

串行垃圾回收器

使用单线程进行垃圾回收,在回收的时候应用程序需要暂停执行。新生代通常采用复制算法,老生代通常采用标记压缩算法。串行回收典型的垃圾回收活动图如下所示:

并行回收回收器

使用多线程进行垃圾回收,在回收的时候应用程序需要暂停,新生代通常采用复制算法,老生代通常采用标记压缩算法。垃圾回收活动图如下:

CMS

整个回收期间划分成多个阶段:初始标记、并发标记、重新标记、并发清除等阶段。在初始标记和重新标记阶段需要暂停应用程序,在并发标记和并发清除期间可以和应用程序并发运行。这个算法通常适用于老生代,新生代可以采用并行复制回收,也可以采用串行复制算法。垃圾回收活动图如下:

同样在老生代回收时,因为是并发执行,当在内存分配时发现如果内存不足需要进行Full GC,也需要STW对整个内存进行串行回收。

G1

G1新生代的收集方式是并行收集,采用复制算法。与其他JVM垃圾回收器一样,一旦发生一次新生代回收,整个新生代都会被回收。这也就是我们常说的新生代回收(Young GC,简称为YGC)。但是G1和其他垃圾回收器不同的地方在于:一、G1会根据预测时间动态的改变新生代的大小;二、G1老生代的垃圾回收方式与其他JVM垃圾回收器对老生代处理有着极大的不同。G1老生代的收集不会为了释放老生代的空间对整个老生代做回收。相反,在任意时刻只有一部分老生代分区会被回收,并且,这部分老生代分区将在下一次增量回收时与所有的新生代分区一起被收集。这就是我们所说的混合回收(Mixed GC),在选择老生代分区的时候,优先考虑垃圾多的分区。

老生代分区的选择就涉及到G1的并发标记算法,这个过程称为“并发标记阶段”,并发标记是是指并发标记线程和应用程序线程同时运行,它有4个典型的子阶段:初始标记子阶段、并发标记子阶段、再标记子阶段和清理子阶段。

1) 初始标记子阶段

负责标记所有能被直接可达的根对象(栈对象、全局对象、JNI对象等),根是对象图的起点,因此初始标记需要将Mutator线程暂停掉,也就是需要一个STW的时间段。在混合回收中的初始标记和新生代的初始标记几乎一样。实际上混合回收的初始标记是借用了新生代回收的结果,即新生代垃圾回收后的新生代Survivor分区作为根,所以混合回收一定发生在新生代回收之后,且不需要再进行一次初始标记。这就是所谓的“借道”。

2) 并发标记子阶段

当YGC执行结束之后,如果发现满足并发标记的条件之后,并发线程就开始进行并发标记。根据新生代的Survivor分区以及老生代的RSet(RSet是记录对象的引用关系,目的是加快活跃对象的遍历)开始并发标记。并发标记的时机是在YGC后,只有内存消耗达到一定的阈值后,才会触发。在G1中这个阈值通过参数InitiatingHeapOccupancyPercent控制(默认值是45,表示的是当已经分配的内存加上即将分配的内存超过内存总容量的45%就可以开始并发标记)。多个并发标记线程启动,每个线程每次只扫描一个分区,从而标记出存活对象。在标记的时候还会计算存活的数量,同时会被计算字节数,并计入分区空间。

并发标记子阶段会对所有的分区的对象进行标记。这个阶段并不需要STW,故标记线程和应用程序线程并发运行。使用Snapshot-At-The-Begging(简称为SATB)算法进行并发标记。

3) 再标记子阶段

再标记是最后一个标记阶段。在该阶段中,G1需要一个STW的时间段,找出所有未被访问的存活对象,同时完成存活内存数据计算。引入该阶段的目的,是为了能够达到结束标记的目标。

4) 清理子阶段

再标记子阶段之后是清理子阶段,该子阶段也是需要一个STW的时间段。清理子阶段主要执行以下操作:

  • 统计存活对象,统计的结果将会用来排序分区,以用于下一次的垃圾回收时分区的选择;
  • 交换标记位图,为下次并发标记准备;
  • 把空闲分区放到空闲分区列表中;这里的空闲指的是全都是垃圾对象的分区;如果分区还有任何活跃对象都不会释放,真正释放是在Mixed GC中;

在并发标记阶段完成之后,在下一次进行垃圾回收的时候就会把垃圾比较多的老生代分区进行回收。这时进行垃圾回收称为混合回收,混合回收和YGC最大的区别就是不仅仅回收所有的新生代分区,也回收部分垃圾多的老生代分区,所以JVM在实现时混合回收重用了YGC所有的代码,两者不同就是是否回收老生代分区。整个G1 GC的活动图如下所示:

注意,在上图并发标记阶段中还可以发生YGC(可以是一次YGC,也可以是多次YGC);另外在图中混合回收也可能发生多次,因为G1对停顿时间有要求的,G1会预测停顿时间决定一次回收老生代分区的数目,所以可能需要多次混合回收才能完成并发标记阶段识别的垃圾比较多的老生代分区。

最后同样在垃圾回收过程或者并发执行过程中,如果内存不足需要进行Full GC时,也需要STW对整个内存进行串行回收。在Java 10对Full GC做了改进,把串行回收改进成并行回收,注意是并行的Full GC,而不是并发回收。

(完)

本文摘编自《JVM G1源码分析和调优》,经出版方授权发布。

适读人群:全部Java工程师

详细分析G1的基本运行原理以及调优方法,讲解细腻,图示丰富,可帮助Java工程师深入理解垃圾回收技术。

  • 图文并茂——作者亲手绘制了大量图片,使读者能直观看懂G1的底层原理。
  • 深入浅出——分步骤解析新生代回收、混合回收、Full GC等原理,还绘制了典型过程的流程图。
  • 讲解细腻——对典型代码进行了大量注释,使读者更深入了解垃圾回收的实现原理。
  • 实操性强——不仅列出了G1的各种参数,而且给出了实际操作中的各种权衡思路。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java团长 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 内存管理
  • 回收算法
    • 串行垃圾回收器
      • 并行回收回收器
        • CMS
          • G1
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档