强大的G1收集器

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

一、Garbage First收集器

Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集 器面向局部收集的设计思路和基于Region的内存布局形式。G1发展到jdk8的时候提供了并发的类卸载支持,补 全了其计划功能的最后一块拼图。这个版本以后的G1收集器才被Oracle官方称为**“全功能的垃圾收集 器”**

G1是一款主要面向服务端应用的垃圾收集器。HotSpot开发团队最初赋予它的期望是(在比较长 期的)未来可以替换掉JDK 5中发布的CMS收集器。现在这个期望目标已经实现过半了,JDK 9发布之 日,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器,而CMS则 沦落至被声明为不推荐使用(Deprecate)的收集器。

基于标记-整理算法实现垃圾收集

1、停顿时间模型

作为CMS收集器的替代者和继承人,设计者们希望做出一款能够建立起“停顿时间模型”(Pause Prediction Model)的收集器,停顿时间模型的意思是能够支持指定在一个长度为M毫秒的时间片段 内,消耗在垃圾收集上的时间大概率不超过N毫秒这样的目标。

为了实现这一目标,G1收集器跳出了他前面所有收集器的樊笼,垃圾收集的目标要么是整个新生代(Minor GC),要么就是整个老 年代(Major GC),再要么就是整个Java堆(Full GC)。而G1收集器可以面向堆内存的任何部分来组成回收集进行回收。衡量标准不再是它属于哪个分代,而 是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。

2、基于Region

虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有很大区别。

1.1、分区与分代

G1不再坚持固定大小以及固定数量的 分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor间,或者老年代空间。收集器能够对扮演不同角色的 Region采用不同的策略去处理。虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区 域(不需要连续)的动态集合。

1.2、大对象与Humongous区域

Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个 Region容量一半的对象即可判定为大对象。
每个Region的大小可以通过参数**-XX:G1HeapRegionSize**设 定,取值范围为1MB~32MB,且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象, 将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代 的一部分来进行看待

在这里插入图片描述

二、优先级回收

G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作 为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍。

G1收集器去跟踪各个Region里面的垃 圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一 个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数**-XX:MaxGCPauseMillis**指定,默 认值是200毫秒),优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。 这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获 取尽可能高的收集效率

三、G1实现java堆化整为零的关键

1、将Java堆分成多个独立Region后,Region里面存在的跨Region引用对象如何解决?

G1采用机记忆集避免全堆GC Roots扫描,但G1的记忆集要复杂的很多,每个Region都维护自己的记忆集,这些记忆集会记录下别的Region指向自己的指针,并标记这些指针分别在哪些卡页的范围之内。G1的记忆集在存储结构的本质上是一 种哈希表,Key是别的Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号。这 种“双向”的卡表结构(卡表是“我指向谁”,这种结构还记录了“谁指向我”)。

因为一些更复杂的数据结构如卡表,以及比其他收集器明显更多的分代数量。G1收集器要比其他的传统垃 圾收集器有着更高的内存占用负担。G1至少要耗费大约相当于Java堆容量10%至20%的额 外内存来维持收集器工作。

2、在并发标记阶段如何保证收集线程与用户线程互不干扰地运行?

因为并发收集时,标记线程和用户线程同时运行,如何能保证标记时已经标记的对象在用户线程里再次被引用。在这里CMS和G1有不同的实现方式:CMS收集器采用增量更新算法实现,而G1 收集器则是通过原始快照(SATB)。如果不了解增量更新和原始快照参照我的另一篇博客。

G1为每一个Region设 计了两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过 程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。默认这两个指针之上的对象它们是存活的,不纳入回收范围。

与CMS中 的“Concurrent Mode Failure”失败会导致Full GC类似,如果内存回收的速度赶不上内存分配的速度, G1收集器也要被迫冻结用户线程执行,导致Full GC而产生长时间“Stop The World”。

3、怎样建立起可靠的停顿预测模型?
用户通过-XX:MaxGCPauseMillis参数指定的停顿时间 只意味着垃圾收集发生之前的期望值。

G1收集器的停顿 预测模型是以衰减均值(Decaying Average)为理论基础来实现的,在垃圾收集过程中,G1收集器会记 录每个Region的回收耗时、每个Region记忆集里的脏卡数量等各个可测量的步骤花费的成本,并分析得 出平均值、标准偏差、置信度等统计信息。然后通过这些数据来分析回收那一块Region能获得最高的收益。

四、收集过程

1、初始标记

仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要 停顿线程,但耗时很短。

2、并发标记

从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以 后,还要重新处理SATB(原始快照)记录下的在并发时有引用变动的对象。

3、最终标记

对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SATB记录。

4、筛选回收

负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。

考虑到G1不是仅仅面向低延迟,停顿用户线程能够最大幅度提高垃圾收集效率,为了保 证吞吐量所以才选择了完全暂停用户线程的实现方案。

在这里插入图片描述

五、G1 和CMS比较

G1和CMS都使用卡表来处理跨代指针,但G1的卡表实现更为复杂,而且 堆中每个Region,无论扮演的是新生代还是老年代角色,都必须有一份卡表,这导致G1的记忆集(和 其他内存消耗)可能会占整个堆容量的20%乃至更多的内存空间;相比起来CMS的卡表就相当简单, 只有唯一一份,而且只需要处理老年代到新生代的引用,反过来则不需要

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值