G1收集器—Garbage First Collector
官方文档地址:点击访问
1 背景
- 吞吐量
吞吐量关注的是,在一个指定的时间内最大化一个应用的工作量
如下方式来衡量一个系统吞吐量的好坏- 在一小时内同一个事务(或者任务, 请求)完成的次数(tps)
- 数据库一小时可以完成多少次查询
对于关注吞吐量的系统,卡顿是可以接受的,因为这个系统关注长时间的大量任务的执行能力,单次快速的响应并不值得考虑
- 响应能力
响应能力—一个程序或者系统对请求是否能够及时响应,比如- 一个桌面UI能多块地响应一个事件
- 一个网站能够多快返回查询的数据
- 数据库能够多快的返回查询的数据
对于这列对相应能力敏感的场景,长时间的停顿是无法接受的
2 简介
- G1收集器是一个面向服务端的垃圾收集器,适用于多核处理器,大内存容量的服务器
- 它满足短时间GC停顿的同时达到一个较高的吞吐量
- JDK 7以上版本适用
设计目标
- 与应用线程同时工作,几乎不需要STW(与CMS类似)
- 整理剩余空间,不产生内存碎片(CMS只能在Full GC时,用STW整理内存碎片)
- GC停顿更加可控
- 不牺牲系统的吞吐量
- GC不要求额外的内存空间(CMS需要预留空间存储浮动垃圾)
设计规划—替换掉CMS
G1在某些方面弥补了CMS的不足,比如
- CMS使用的是Mark-Sweep算法,自然会产生垃圾碎片
- G1基于Copying算法,高效的整理剩余内存,而不要管理内存碎片
另外,G1提供多种手段,以达到对GC停顿时间的可控
- Hotspot虚拟机的主要构成
3 G1收集器的堆结构
- 整个堆被划分为一个个相等的不连续的内存区域(regions),每个region都有一个分代的角色
- Eden
- Survivor
- Old
- 对每个角色的数量并没有强制的规定,也就是说对每个分代内存的大小,可以动态变化
- G1最大的特点就是高效的执行回收,优先去执行那些大对象可回收的区域(region)
G1使用了GC停顿时间可预测的模型来满足用户设定的GC停顿时间,根据用户设定的目标时间,G1会自动的选择哪些region要清除,一次清除多少个region
G1从多个region中复制存活的对象,然后集中放入到一个region, 同时清除, 整理内存(copying收集算法)
4 G1对比其他垃圾收集器
- 对比使用Mark-Sweep的CMS, G1使用的copying算法不会造成内存碎片
- 对比Parallel Scavenge(基于copying), Parallel Old收集器(基于Mark-Compact ),Parallel会对整个区域做整理导致GC停顿时间会比较长,而G1只是特定的整理几个region
G1并非一个实时的收集器, 与Parallel Scavenge一样, 对GC停顿时间的设置并不绝对生效, 只是G1有较高的几率保证不超过设定的GC停顿时间.
与之前的GC收集器对比,G1会根据用户设定的GC停顿时间,只能评估哪几个region需要被回收可以满足用户的设定
6 相关概念
6.1分区
- 分区(Region)—G1采取了不同的策略来解决并行, 串行和CMS收集器的碎片, 暂停时间不可控等问题----G1将
整个堆
分成相同大小的分区(Region)
每个分区都可能是年轻代也可能是老年代, 但是同一时刻只能属于某个代
年轻代, 幸存区,老年代这些概念还存在,成为逻辑上的概念
, 这样方便复用之前分代框架的逻辑
在物理上不需要连续,则带来了额外的好处—有些分区垃圾对象特别多,有的分区垃圾对象很少,G1会优先回收垃圾对象特别多的分区
,这也就是G1名字的由来,及首先收集拉结最多的分区
依然是在新生代满了的时候,对这个新生代进行回收—整个新生代中的对象,要么被回收,要么被晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小
G1还是一种带压缩的收集器,在回收老年代的分区时,是将存活的对象从一个分区拷贝到另外一个分区,这个拷贝就实现了局部的压缩
6.2 收集集合CSet
一组可被回收分区的集合
在Cset中存活的数据会在GC过程中被移动到另一个可用分区
Cset中分区可以来自Eden空间
, Survivor空间
或者老年代
6.3 已记忆集合RSet
RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)
Rset的价值在于使得垃圾收集器不需要扫描整个堆栈谁引用了当前分区中的对象,只需要扫描Rset即可
如下图所示
Region1和Region3中的对象都引用了Region2中的对象,一次在Region2的Reset中记录这两个引用
G1 GC是在points-out的card table之上再加了一层结构来构成points-into Rest—每个region会记录下到底哪些别的region有指向自己的指针,而这些指针分别在哪些card的范围内
这个Rest其实是一个hash table,key是别的region的起始地址,value是一个集合,里面的元素是card table的index.
举例来说,如果region A的Rset里有一项的key是region B,value里有index为1234的card
那么意思就是region B的一个card里有引用指向region A
所以对region A来说,该Rset记录的就是pints-into的关系;
而card table仍然记录points-out的关系
6.4 起始快照SATB(Snapshot-At-The-Beginning)
SATB是G1 GC在并发标记阶段使用的增量式的标记算法
并发标记是并发多线程的,但并发现在同一时刻只扫描一个Region
7 G1相对于CMS的优势
- G1在压缩空间方面有优势
- G1通过将内存空间分成区域(Region)的方式避免内存碎片问题
- Eden, Survivor和Old区不在固定,在内存使用效率上来说更灵活
- G1可以通过设置与其停顿时间(Pause Time)来控制垃圾收集时间,避免应用雪崩现象
- G1在回收内存后会立即同时做合并空闲内存的工作,而CMS默认是在STW的时候做
- G1会在Young GC中使用,而CMS只能在Old区使用
8 G1的适合场景
- 服务端多核CPU, JVM内存占用较大的应用
- 应用在运行过程中会产生大量内存碎片,需要经常压缩空间
- 想要更可控,可预期的GC停顿周期:防止高并发下应用的雪崩现象
9 G1 GC模式
G1提供了两种GC模式—两者都是完全的STW
Young GC
- 选定的所有年轻代里的Region
- 通过控制年轻代的Region个数,及年轻代内存大小,来控制Young GC的时间开销
Mixed GC
- 选定所有年轻代里的Region,外加根据
全局并发标记
global concurrent marking统计得出收益高的若干老年代Region - 在用户指定的开销目标范围内尽可能选择收益高的老年代Region
- 选定所有年轻代里的Region,外加根据
需要注意的是
Mixed GC不是Full GC,它只能回收部分老年代的Region
如果Mixed GC是在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用Serial Old GC(Full GC)来收集这个GC Heap
所以本质上,G1是不提供Full GC的
10 全局并发标记—global concurrent marking
其执行过程类似于CMS, 但是不同的是,在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必要环节
global concurrent marking的执行过程分为四个步骤
初始标记(initial mark, STW)
—它标记了从GC Root开始直接可达的对象并发标记(Concurrent Maring)
这个阶段从GC Root开始对heap中的对象进行标记, 标记线程与应用线程并发执行, 并且收集各个Region的存活对象信息重新标记(Remarking STW)
—标记那些在并发标记阶段发生变化的对象, 将被回收清理(Cleanup)
—清除空Region(没有存活对象的), 加入到free list
其中
-
第一阶段initial mark是共用了Young GC的暂停,这是因为他们可以复用root scan操作, 所以可以说global concurrent marking是伴随Young GC发生的
-
第四阶段Cleanup只是回收了没有存活对象的Regin,所以它并不需要STW
11 G1在运行过程中的主要模式
- YGC(不同于CMS)----Young GC
在Eden充满时触发,在回收之后所有之前属于Eden的区块全部变成空包,既不属于任何一个分区(Eden, Survivor或Old) - 并发阶段
- Mixed GC混合模式
- 触发时机—由一些参数控制,另外也空着这哪些老年代Region会被选入Cset(收集集合)
G1HeapWastePercent
在GCM结束之后,我们可以找到old gen regions中有多少空间要被回收,在每次YGC之后和每次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GCG1MixedGCLiveThresholdPercent
old gen region中存活的对象的占比,只有在此参数之下,才会被选入CsetG1MixedGCCountTarget
一次GCM之后,最多执行Mixed GC的次数G1OldCetRegionThresholdPercent
一次Mixed GC中能被选入Cset的最多old gen region数量
- 触发时机—由一些参数控制,另外也空着这哪些老年代Region会被选入Cset(收集集合)
- Full GC(一般是G1出现问题时发生)
12 G1收集器概览
G1收集器将堆划分为若干个区域Region,它仍然属于分代收集器,不过,这些区域的一部分包含新生代
新生代的垃圾收集依然采用暂停所有应用线程的方式,讲存活对象拷贝到老年代或者Survivor空间
老年代也分成很多区域,G1收集器通过将对象从一区域负值到另外一个区域,完成了清理工作
这就意味着,在正常处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片的问题
存在了
13 Humongous区域
在G1中,还有一种特殊的区域,叫Humongous
区域
如果一个对象占用的空间达到或者是超过了分区容量的50%以上, G1收集器就认为这是一个巨型对象
这些巨型对象, 默认直接会被分配到老年代
,但是如果他是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响
为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象
如果一个H区装不下一个巨型对象,那么G1会寻找连续的的H区来存储
为了找到连续的H区,有时候不得不启动Full GC
14 G1 Young GC
G1 Young GC主要是对Eden区进行GC,它在Eden空间耗尽时被触发
在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分会直接晋升到老年代空间
Survivor区中的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中
,最终Eden空间的数据为空
,GC完成后,应用线程继续执行
如果仅仅GC新生代对象,我们如果找到所有的跟对象呢?老年代所有的对象都是根么?那这样扫描下来会耗费大量的时间
于是,G1引进了Rset
为概念—它的全称是Remember Set,作用是跟踪指向某个Heap区内的对象引用
在CMS中, 也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用—这是一种points-out,在进行Young GC扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代
但在G1中,并没有使用points-out,这是由于一个分区太小,分区数量太多,如果使用points-out的haul,会造成大量的空间浪费,有些根本不需要GC的分区引用也扫描了
于是G1中使用points-in来解决, points-in的意思是哪些分区引用了当前分区中的对象,这样,仅仅将这些对象当做根来扫描就避免了无效的扫描
由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可
14.1 Card Table—卡表
需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1中又引入了另外一个概念,卡表Card Table
一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡
卡通常较小,介于128到512字节之间,
Card Table通常为字节数组,有Card的索引(即数组下标)来标识每个分区的空间地址
在默认情况下,每个卡都未被引用.当一个地址空间被引用时,这个地址空间对应的数组索引的值被标为’0’—标记为脏被引用
一般情况下,这个Rset其实是一个Hash Table, key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index
14.2 Young GC步骤
-
阶段1: 根扫描
- 静态和本地对象被扫描
-
阶段 2: 更新RS
- 处理dirty card队列更新RS
-
阶段3: 处理RS
- 检测从年轻代指向老年代的对象
-
阶段4: 对象拷贝
- 拷贝存活的读写到Survivor或Old区域
-
阶段5: 处理引用队列
- 软引用处理
- 弱引用处理
- 虚引用处理
15 Mixed GC—混合式GC
Mixed GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区
它的GC步骤分为两步
- 全局并发标记—Global Concurrent Marking
- 其中并发标记采用三色标记算法
- 拷贝存活对象—Evacuation
G1到现在可以找到哪些老的分区可回收垃圾最多. 当全局并发标记完成后, 在某个时刻,就开始了Mixed GC
这些垃圾回收被称为"混合式"是因为他们不仅仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的分区
混合式GC也是采用复制清理策略
,当GC完成后,会重新释放空间
16 G1分代算法
为老年代设置分区的目的是老年代里有的分区垃圾多,有的分区垃圾少,这样在回收的时候可以专注于收集垃圾多的区域,这也是G1名称的由来
不多这个算法并不适合新生代垃圾收集, 因为新生代的垃圾收集算法是复制算法,但是`新生代也是用分区机制主要是因为便于代大小的调整