G1 垃圾收集器原理详解

本文深入探讨了G1垃圾收集器,旨在替代CMS收集器以解决其缺点。G1采用‘标记-整理’算法,避免内存碎片,并能精确控制停顿时间。文章介绍了G1的内存模型,包括Region、Remember Set、Card Table和Write Barrier等概念,以及对象分配策略。G1的垃圾回收过程分为Young GC和Mixed GC,其中 Mixed GC 通过全局并发标记和拷贝存活对象来回收内存。文章还讨论了三色标记算法及其异常情况,并总结了G1的优缺点。
摘要由CSDN通过智能技术生成

1、CMS 垃圾收集器的缺陷:
        JVM 团队设计出 G1 收集器的目的就是取代 CMS 收集器,因为 CMS 收集器在很多场景下存在诸多问题,缺陷暴露无遗,具体如下:

(1)CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4

(2)CMS收集器无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自然会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,称为“浮动垃圾”,CMS 无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。

(3)由于垃圾收集阶段会产生“浮动垃圾”,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以参数 -XX:CMSInitiatingOccupancyFraction 设置的过高将会很容易导致 “Concurrent Mode Failure” 失败,性能反而降低。

(4)CMS是基于“标记-清除”算法实现的收集器,会产生大量不连续的内存碎片。当老年代空间碎片太多时,如果无法找到一块足够大的连续内存存放对象时,将不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full  GC之后增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full  GC之后,跟着来一次碎片整理过程。

2、G1 垃圾收集器的特点:
        G1(Garbage First)收集器是 JDK7 提供的一个新收集器,在 JDK9 中更被指定为官方GC收集器,与CMS收集器相比,最突出的改进是:

基于 “标记-整理” 算法,收集后不会产生内存碎片。
可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
在介绍G1的垃圾收集流程之前,我们先简单了解下G1中的内存模型以及主要的数据结构,这些数据结果对我们了解G1的垃圾回收流程十分重要

二、G1 垃圾收集器的内存模型:
        G1 收集器不采用传统的新生代和老年代物理隔离的布局方式,仅在逻辑上划分新生代和老年代,将整个堆内存划分为2048个大小相等的独立内存块Region,每个Region是逻辑连续的一段内存,具体大小根据堆的实际大小而定,整体被控制在 1M - 32M 之间,且为2的N次幂(1M、2M、4M、8M、16M和32M),并使用不同的Region来表示新生代和老年代,G1不再要求相同类型的 Region 在物理内存上相邻,而是通过Region的动态分配方式实现逻辑上的连续。

        G1收集器通过跟踪Region中的垃圾堆积情况,每次根据设置的垃圾回收时间,回收优先级最高的区域,避免整个新生代或整个老年代的垃圾回收,使得stop the world的时间更短、更可控,同时在有限的时间内可以获得最高的回收效率。

        通过区域划分和优先级区域回收机制,确保G1收集器可以在有限时间获得最高的垃圾收集效率。

1、分区Region:

 G1 垃圾收集器将堆内存划分为若干个 Region,每个 Region 分区只能是一种角色,Eden区、S区、老年代O区的其中一个,空白区域代表的是未分配的内存,最后还有个特殊的区域H区(Humongous),专门用于存放巨型对象,如果一个对象的大小超过Region容量的50%以上,G1 就认为这是个巨型对象。在其他垃圾收集器中,这些巨型对象默认会被分配在老年代,但如果它是一个短期存活的巨型对象,放入老年代就会对垃圾收集器造成负面影响,触发老年代频繁GC。为了解决这个问题,G1划分了一个H区专门存放巨型对象,如果一个H区装不下巨型对象,那么G1会寻找连续的H分区来存储,如果寻找不到连续的H区的话,就不得不启动 Full GC 了。

2、Remember Set:
        在串行和并行收集器中,GC时是通过整堆扫描来确定对象是否处于可达路径中。然而G1为了避免STW式的整堆扫描,为每个分区各自分配了一个 RSet(Remembered Set),它内部类似于一个反向指针,记录了其它 Region 对当前 Region 的引用情况,这样就带来一个极大的好处:回收某个Region时,不需要执行全堆扫描,只需扫描它的 RSet 就可以找到外部引用,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况,而这些引用就是 initial mark 的根之一。

        事实上,并非所有的引用都需要记录在RSet中,如果引用源是本分区的对象,那么就不需要记录在 RSet 中;同时 G1 每次 GC 时,所有的新生代都会被扫描,因此引用源是年轻代的对象,也不需要在RSet中记录;所以最终只需要记录老年代到新生代之间的引用即可。

3、Card Table:
        如果一个线程修改了Region内部的引用,就必须要去通知RSet,更改其中的记录。需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,因此 G1 回收器引入了 Card Table 解决这个问题。

        一个 Card Table 将一个 Region 在逻辑上划分为若干个固定大小(介于128到512字节之间)的连续区域,每个区域称之为卡片 Card,因此 Card 是堆内存中的最小可用粒度,分配的对象会占用物理上连续的若干个卡片,当查找对分区内对象的引用时便可通过卡片 Card 来查找(见RSet),每次对内存的回收,也都是对指定分区的卡片进行处理。每个 Card 都用一个 Byte 来记录是否修改过,Card Table 就是这些 Byte 的集合,是一个字节数组ÿ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青藤伽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值