G1相关补充

[TOC]

G1收集概览:

G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了。

Humongous区域:

在G1中,还有一种特殊的区域,叫Humongous区域。如果一个对象占用的空间达到或者超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

G1 Young GC:

  • Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC完成工作,应用线程继续执行。
  • 如果仅仅GC新生代对象,我们如何找到所有的根对象呢?老年代的所有对象都是根么?那这样扫描下来会耗费大量的时间。于是,G1引进了RSet的概念。它的全称是Remembered Set,作用是跟踪指向某个heap区内的对象引用。下面看图:
    img
  • 在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out,在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代。
  • 但在G1中,并没有使用point-out,这是由于一个分区太小,分区数量太多,如果是用point-out的话,会造成大量的扫描浪费,有些根本不需要GC的分区引用也扫描了。
  • 于是G1中使用point-in来解决。point-in的意思是哪些分区引用了当前分区中的对象。这样,仅仅将这些对象当做根来扫描就避免了无效的扫描。
  • 由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的,原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用既可。
  • 需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1中又引入了另外一个概念,卡表(Card Table)。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(既数组下标)来标识每个分区的空间地址。
  • 默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为“0”,既标记为被引用,此外RSet也将这个数组下标记录下来。一般情况下,这个RSet其实是一个Hash Table,key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。

另外它分为下几个阶段:

  • 阶段1:根扫描
    表态和本地对象被扫描
  • 阶段2:更新RS
    处理dirty card队列更新RS
  • 阶段3:处理RS
    检测从年轻代指向老年代的对象
  • 阶段4:对象拷贝
    拷贝存活的对象到survivor/old区域
  • 阶段5:处理引用队列
    软引用、弱引用、虚引用处理

再谈Mixed GC:

  • Mixed GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。
  • 这的GC步骤分为两步:
    1、全局并发标记(global concurrent marking)
    2、拷贝存活对象(evacuation)
  • 在G1 GC中,global concurrent marking主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为四个步骤,这个在之前已经学习过了,回忆一下:
    img

三色标记算法:

  • 什么是三色标记?

    为啥需要三色标记呢?

    相比之前的标记清除算法,其GC执行期间需要把整个程序完全暂停,不能异步执行GC操作。对实时性要求比较高的系统来说,这种需要长时间挂起的标记清除算法是不可接受的,而三色标记算法就很好的解决了这个问题。

    三色标记最大的好处是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断操作来进行整个GC。

    三色标记具体指那三色?

    白色、灰色和黑色。

    黑色:根对象,或者该对象与它的子对象都被扫描过。

    灰色:对象本身被扫描,但是还有没扫描该对象的子对象。

    白色:未被扫描的对象,如果扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象。

    并发出现“对象消失”问题

    img

    什么时候,会产生对象消失“的问题”,即原本应该是黑色的对象被误标为白色?

    当且仅当以下两个条件同时满足:

    赋值器插入了一条或多条从黑色对象到白色对象的新引用;

    赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。

    那如何解决并发扫描时对象消失问题?

    只需要破坏这两个条件任意一个即可,两种解决方案:增量更新(Increamental Update)和原始快照(Snap shot At The Begining, SATB)

    CMS 基于 增量更新(Increamental Update)来做并发标记,G1 基于 原始快照(Snap shot At The Begining, SATB)来实现的。

    增量更新(Increamental Update):当一个白色对象被黑色对象引用,将黑色对象重新标记为灰色,让垃圾回收器重新扫描。

    原始快照(Snap shot At The Begining, SATB):原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过得引用关系中的灰色对象为根,重新扫描一次。总而言之就是:无论引用关系删除与否,都会按照刚刚开始扫描的那一刻的对象图快照来进行搜索。

    对比增量更新和原始快照:

    原始快照关注的是引用删除,增量更新关注的是引用增加。

    为啥G1不使用增量更新算法呢?

    因为使用增量更新算法,那变成灰色的对象还要重新扫描一遍,效率太低了,所以G1在处理并发标记的过程比CMS效率要高,这个主要是解决漏标的算法决定的。