GC垃圾回收(3)-|GC垃圾回收(3)- 三色标记算法

1. CMS CMS过程在上篇文章 GC垃圾回收(2) 中已经写过。
它分为四个阶段:

1 initial mark (初始标记)
2 concurrent mark (并发标记)
3 remark (重新标记)
4 concurrent sweep (并发清理)
其中 并发标记 阶段会有漏标的问题,为解决这个问题,采用了 "三色标记算法"
2. G1 G1 GC(Garbage First Garbage Collector)是一种服务端应用使用的垃圾收集器,目标是用在 多核、大内存的机器上,它在大多数情况下可以实现指定的GC暂停时间,同时还能保持较高的吞吐量。它的吞吐量相较PS+PO降低了大概10%~15%,但是大大降低了响应时间,大概200ms的程度
2.1 G1内存模型 G1内存模型如下:

GC垃圾回收(3)-|GC垃圾回收(3)- 三色标记算法
文章图片
G1模型.png G1相较之前其它的垃圾回收器,对模型进行了改变,不再进行物理分代,采用逻辑分代。
它不再将连续内存分为Eden区和Old区,而是将内存分为一个个的Region。一块Region(分区)在逻辑上依然分代,分为四种:Eden,Old,Survivor,Humongous(大对象,跨多个连续的Region)。
它的每个分区都可能是年轻代也可能是老年代,但是在同一时刻只能属于某个代。
年轻代、幸存区、老年代这些概念还存在,成为了逻辑上的概念,这样方便复用之前分代框架的逻辑。在物理上不需要连续,这带来了额外的好处——有的分区内垃圾对象特别多,有的分区内垃圾对象很少,G1会优先回收垃圾对象特别多的分区,这样可以花费较少的时间来回收这些分区的垃圾,这也就是G1名字的由来,即首先回收垃圾最多的分区。
新生代其实并不适用于这种算法,依然是在新生代满了的时候,对整个新生代进行回收——整个新生代中的对象,要么被回收、要么晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小。
G1还是一种带压缩的收集器,在回收老年代的分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩。每个分区的大小从1M到32M不等,但都是2的幂次方。
特点:
  • 并发收集
  • 压缩空闲时间不会延长GC的暂停时间
  • 更易预测GC的暂停时间
  • 适用不需要实现很高吞吐量的场景
G1与CMS在并发收集时的算法没太大区别,用的是 三色标记 算法。但ZGC和Shenandoah使用的是 颜色指针 Colored Pointers。
2.2 基本概念 2.2.1 CSet
  • CSet = Collection set
  • 一组可被回收的分区的集合。
    在ZSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自Eden空间、survivor空间、或者老年代。CSet会占用不到整个堆空间的1%大小。
2.2.2 Card Table
主要用于分代模型中帮助垃圾回收。
为什么需要 card table ?
寻找存活对象并不是一件容易的事。从一个GC root对象寻找,可能被Old区对象引用,这个Old区对象又被Eden区对象引用,那么判断Eden区对象是否存活就需要遍历整个Old区存活对象看是否被Old区对象引用。这样的话每进行一次YGC就要扫描整个Old区。
所以JVM内部,将内存区域分为一个个的card,对象存在一个个的card里。当老年代某个card中的对象指向了年轻代,就会将这个card标记为 Dirty 。这么多card具体哪个是 Dirty的,用位图BitMap来代表(如0110010010,1表示Dirty),这就是Card Table。
Card Table :由于做YGC时,需要扫描整个Old区,效率非常低,所以JVM设计了Card Table, 如果一个Old区Card Table中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card。 在结构上,Card Table用BitMap来实现。
2.2.3 RSet
  • RSet = Remembered Set
  • 记录了其他Region中的对象到本Region的引用
    RSet的价值在于:使得垃圾收集器不需要扫描整个堆,找到谁引用了当前分区中的对象,只需要扫描RSet即可。
RSet会占用一定的空间,所以ZGC又做了改进,不使用RSet,用颜色指针来标记。
Rset与赋值的效率:
  • 由于RSet的存在,那么每次给对象赋引用的时候,就要做一些额外的操作
  • 额外的操作,指的是在RSet中做一些额外的记录(在GC中被称为写屏障)
  • 这个写屏障 不是 内存屏障
2.2.4 新老年代比例
5% ~ 60%(新生代)
  • 一般不用手动指定
  • 也不要手动指定,因为这是G1预测停顿时间的基准
G1能跟踪STW停顿时间,根据停顿时间动态调整新生代(Y区)比例
2.2.5 humongous object
【GC垃圾回收(3)-|GC垃圾回收(3)- 三色标记算法】超过单个region的 50% 就是一个大对象,也可跨越多个region。
2.3 GC何时触发
  • YGC
    • Eden区空间不足
    • 多线程并行执行
  • FGC
    • Old空间不足
    • System.gc()
注意:G1也是存在FGC的,并且一定会被触发。当对象分配不下是会产生FGC。
2.3.1 如果G1产生FGC,应该做什么,如何优化?
  1. 扩内存
  2. 提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
  3. (主要)降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)
2.3.2 G1中的MixedGC
  • 相当于CMS
  • XX:InitiatingHeapOccupacyPercent
    --默认值45%
    --当堆内存超过这个值,触发MixedGC
回收时不分新生代还是老年代什么的,region满了就回收。
MixedGC过程:
  1. 初始标记 STW
  2. 并发标记
  3. 最终标记 STW(重新标记)
  4. 筛选回收 STW(并行)
跟CMS非常像,MixedGC最后是筛选回收,多了个筛选步骤。筛选就是找出垃圾最多的region。筛选后将存活对象复制到其他region,再将之前的region清空。
2.4 并发标记算法 CMS和G1在并发标记时使用的是同一个算法:三色标记法,使用白灰黑三种颜色标记对象。白色是未标记;灰色自身被标记,引用的对象未标记;黑色自身与引用对象都已标记。

GC垃圾回收(3)-|GC垃圾回收(3)- 三色标记算法
文章图片
三色标记法.png
2.4.1 漏标问题
在remark过程中,黑色指向了白色,如果不对黑色重新扫描,则会漏标。会把白色D对象当作没有新引用指向从而回收掉。
GC垃圾回收(3)-|GC垃圾回收(3)- 三色标记算法
文章图片
漏标问题.png 并发标记过程中,Mutator删除了所有从灰色到白色的引用,会产生漏标。此时白色对象应该被回收
产生漏标问题的条件有两个:
1.黑色对象指向了白色对象
2.灰色对象指向白色对象的引用消失
所以要解决漏标问题,打破两个条件之一即可:
  1. 跟踪黑指向白的增加
    incremental update:增量更新,关注引用的增加,把黑色重新标记为灰色,下次重新扫描属性。CMS采用该方法。
  2. 记录灰指向白的消失
    SATB snapshot at the beginning:关注引用的删除,当灰-->白消失时,要把这个 引用 推到GC的堆栈,保证白还能被GC扫描到。G1采用该方法。
为什么G1采用SATB而不用incremental update?
因为采用incremental update把黑色重新标记为灰色后,之前扫描过的还要再扫描一遍,效率太低。
G1有RSet与SATB相配合。Card Table里记录了RSet,RSet里记录了其他对象指向自己的引用,这样就不需要再扫描其他区域,只要扫描RSet就可以了。
也就是说 灰色-->白色 引用消失时,如果没有 黑色-->白色,引用会被push到堆栈,下次扫描时拿到这个引用,由于有RSet的存在,不需要扫描整个堆去查找指向白色的引用,效率比较高。SATB配合RSet浑然天成。

    推荐阅读