垃圾回收(3)G1的结构和概念

G1介绍

G1(Garbage First)算法,经过参数-XX:+UseG1GC来启用,该算法在JDK 7u4版本被正式推出,G1能够经过参数-XX:MaxGCPauseMillis控制GC暂停时间。算法

Region

在G1算法中,采用了另一种彻底不一样的方式组织堆内存,堆内存被划分为多个大小相等的内存块(Region),每一个Region是逻辑连续的一段内存,结构以下:
垃圾回收(3)G1的结构和概念数组

每一个Region被标记了E、S、O和H,说明每一个Region在运行时都充当了一种角色,其中H是以往算法中没有的,它表明Humongous,这表示这些Region存储的是巨型对象(humongous object,H-obj),当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。数据结构

堆内存中一个Region的大小能够经过-XX:G1HeapRegionSize参数指定,大小区间只能是1M、2M、4M、8M、16M和32M,总之是2的幂次方,默认把堆内存按照2048份均分,也就是默认2048个Region。好比-Xmx16g -Xms16g,G1就会采用16G / 2048 = 8M 的Region。并发

垃圾回收(3)G1的结构和概念

GC模式

G1中提供了三种模式垃圾回收模式,Young gc、Mixed gc 和 Full gc,在不一样的条件下被触发。ide

一、Young gc

发生在年轻代的GC算法,通常对象(除了巨型对象)都是在eden region中分配内存,当全部eden region被耗尽没法申请内存时,就会触发一次young gc,这种触发机制和以前的young gc差很少,执行完一次young gc,活跃对象会被拷贝到survivor region或者晋升到old region中。
年轻代,默认占用堆大小由如下参数控制:
垃圾回收(3)G1的结构和概念post

二、Mixed gc

当愈来愈多的对象晋升到老年代old region时,为了不堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并非一个old gc,除了回收整个young region,还会回收一部分的old region,这里须要注意:是一部分老年代,而不是所有老年代,能够选择哪些old region进行收集,通常是选择回收效益最高的,也就是垃圾最多的region,从而能够对垃圾回收的耗时时间进行控制。性能

mixed gc中也有一个阈值参数 -XX:InitiatingHeapOccupancyPercent,当老年代大小占整个堆大小百分比达到该阈值时,会触发一次mixed gc.优化

三、Full gc

若是对象内存分配速度过快,mixed gc来不及回收,致使老年代被填满,就会触发一次full gc,G1的full gc算法就是单线程执行的serial old gc,会致使异常长时间的暂停时间,须要进行不断的调优,尽量的避免full gc.线程

G1中的数据结构

一、TLAB(线程本地分配缓冲区Thread Local Allocation Buffer)

因为堆内存是应用程序共享的,应用程序的多个线程在分配内存的时候须要加锁以进行同步。为了不加锁,提升性能每个应用程序的线程会被分配一个TLAB。TLAB中的内存来自于G1年轻代中的内存分段。当对象不是Humongous对象,TLAB也能装的下的时候,对象会被优先分配于建立此对象的线程的TLAB中。这样分配会很快,由于TLAB隶属于线程,因此不须要加锁。当TLAB的剩余空间不知足分配需求,则从新申请一块TLAB空间。设计

二、Card (卡表)

一、很小的内存区域,G1将Java堆划分为相等大小的一个个区域,这个小的区域大小是512 Byte,称为Card
二、Card Table维护着全部的Card。Card Table的结构是一个字节数组,Card Table用这个数组映射着每个Card
三、Card中对象的引用发生改变时,Card在Card Table数组中对应的值被标记为dirty,就称这个Card被脏化了
四、分配对象会占用物理上连续若干个卡片

三、Rset(已记忆集合)

一、每一个Region初始化时,会初始化一个remembered set
二、RSet里面记录了引用——就是其余Region中指向本Region中全部对象的全部引用,也就是谁引用了个人对象即RSet须要记录的东西应该是 xx Region的 xx Card。
三、RSet实际上是一个Hash Table,Key是其余的Region的起始地址,Value是一个集合,里面的元素是Card Table 数组中的index,既Card对应的Index,映射到对象的Card地址。
垃圾回收(3)G1的结构和概念
Region1和Region3中有对象引用了Region2的对象,则在Region2的Rset中记录了这些引用。

Rset实现过程

为了维护这些RSet,若是每次给引用类型的字段赋值都要更新RSet,这带来的额外开销实在太大,G1中采用post-write barrier(写后栅栏)和concurrent refinement threads(并发后台线程)实现了RSet的更新。

//假设对象young和old分别在不一样的Region中
Object young = new Object();
old.p = young;

Java层面给old对象的p字段赋值young对象的先后,JVM会插入一个pre-write barrier(写前栅栏)和post-write barrier(写后栅栏)。

一、写前栅栏 Pre-Write Barrrier:即将执行一段赋值语句时,等式左侧对象将修改引用到另外一个对象,那么JVM就须要在赋值语句生效以前,记录丧失引用的对象。
二、写后栅栏 Post-Write Barrrier:当执行一段赋值语句后,等式右侧对象获取了左侧对象的引用,一样须要记录

其中post-write barrier的最终动做以下:
一、找到该字段所在的位置(Card),并设置为dirty_card
二、若是当前是应用线程,每一个Java线程有一个dirty card queue,把该card插入队列
三、除了每一个线程自带的dirty card queue,还有一个全局共享的queue

赋值动做到此结束,接下来的RSet更新操做交由多个ConcurrentG1RefineThread并发完成,每当全局队列集合超过必定阈值后,ConcurrentG1RefineThread会取出若干个队列,遍历每一个队列中记录的card,并进行处理,逻辑以下:

一、根据card的地址,计算出card所在的Region
二、若是Region不存在,或者Region是Young区,或者该Region在回收集合(CSet)中,则不进行处理
三、处理该card中的对象,将应用关系写入Rset中。

RSet有什么好处?
进行垃圾回收时,若是Region1有根对象A引用了Region2的对象B,显然对象B是活的,若是没有Rset,就须要扫描整个Region1或者其它Region,才能肯定对象B是活跃的,有了Rset能够避免对整个堆进行扫描。

四、CSet(回收集合Collection Set)

一、它记录了GC要收集的Regions集合
二、在任意一次收集暂停中,CSet全部分区都会被释放,内部存活的对象都会被转移到分配的空闲Region中。
三、CSet包括须要收集的Eden Regions、Survivor Regions,并且还包括部分(1/8)Old Regions(混合收集的时候,收集年轻代的时候,会收集一部分老年代的Region)。

五、SATB(snapshot-at-the-beginning)

为了解决在并发标记过程当中,存活对象漏标的状况,GC HandBook把对象分红三种颜色:

一、黑色:自身以及可达对象都已经被标记
二、灰色:自身被标记,可达对象还未标记
三、白色:还未被标记

因此,漏标的状况只会发生在白色对象中,且知足如下任意一个条件:

一、并发标记时,应用线程给一个黑色对象的引用类型字段赋值了该白色对象
二、并发标记时,应用线程删除全部灰色对象到该白色对象的引用

对于第一种状况,利用post-write barrier,记录全部新增的引用关系,而后根据这些引用关系为根从新扫描一遍
对于第二种状况,利用pre-write barrier,将全部即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根从新扫描一遍

一、SATB的概念

一、SATB是增量式标记清除垃圾收集器设计的一个标记算法。
二、SATB的标记优化主要针对标记-清除垃圾收集器的并发标记阶段。
三、CMS的i设计使得它在remark阶段必须从新扫描全部线程栈和整个young gen做为root。G1的SATB设计在remark阶段则只须要扫描剩下的satb_mark_queue。

二、SATB的做用

一、SATB保证了在并发标记过程当中新分配对象不会漏标。
二、SATB中的stab_mark_queue,在引用关系发生变化时,利用pre-write barrier,把原引用保存到stab_mark_queue中,每一个应用线程都有一个stab_mark_queue。

总结

SATB的算法,每一个region有5个指针,比较复杂,没有找到太好的文章,没有太理解,还有待进一步了解~~~

相关文章
相关标签/搜索