G1的SATB

  本文受到狼哥占小狼的启发,https://www.jianshu.com/p/9e70097807ba

  在上一篇 G1的GC模式中介绍了在最终标记阶段 会使用SATB算法进行最终的标记 https://www.cnblogs.com/juniorMa/articles/13879270.html。

但关于SATB的一些详细知识还没有介绍,本文就继续介绍什么是SATB,SATB能解决的问题.

一 三色标记法

  先说说三色标记法 

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

  1. 黑色:自身以及可达对象都已经被标记
  2. 灰色:自身被标记,可达对象还未标记
  3. 白色:还未被标记

  G1之所以很快,就是因为G1不会对一个已经是黑色的对象变成灰色,即使它的成员会在并发标记内指向一个新的对象。减少了最终标记的工作量。

  而它能够做到就是依赖SATB算法。

  说回三色标记,三色标记法要解决的问题就是错标和漏标。这里先明确一个概念,在垃圾收集过程中被标记过的才是活对象,没标记的就是垃圾,也就是白色对象。

  错标就是指它是垃圾,结果在本轮没被回收,这个还是可以接受的,毕竟下一轮还是能回收的。

  但是漏标就严重了,一个没有被标记过的对象会被当成垃圾回收,这就会导致程序出错了。

  漏标的情况只会发生在白色对象中,且满足以下任意一个条件:

  1. 并发标记时,应用线程给一个黑色对象的引用类型字段赋值了该白色对象
  2. 并发标记时,应用线程删除所有灰色对象到该白色对象的引用,也就是赋值为NULL

  G1的解决办法是这样的,对于1 利用post-write barrier,记录所有新增的引用关系,然后根据这些引用关系为根重新扫描一遍。那么什么是post-write barrier呢?就是大名鼎鼎的Result Set了。

  对于2 则是利用pre-write barrier,将所有即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根重新扫描一遍。

  这就会让本来应该死的对象活下来,会增加浮动垃圾但是可以减少做标记的工作量加快速度。

二 SATB    

  SATB全称snapshot-at-the-beginning,由Taiichi Yuasa为增量式标记清除垃圾收集器开发的一个算法,主要应用于垃圾收集的并发标记阶段,解决了CMS垃圾收集器重新标记阶段长时间STW的潜在风险。SATB有两大特性:
- Anything live at Initial Marking is considered live.
- Anything allocated since Initial Marking is considered live.

  接下来就详细的解释下这两句话是怎么回事。

  1 Anything live at Initial Marking is considered live.

  初始标记已经活着的对象在整个混合GC周期里都认为是活着的。
  SATB在并发标记中不处理对象的赋新值的场景,只处理一个对象的成员被赋值为NULL的场景。
write_barrier_slot(Object* src, Object** slot, Object* new_ref) { 
    old_ref = *slot;
    if( !is_marked(old_ref) ){
      enqueue(old_ref);
    }
    *slot = new_ref;
}

  此时保存旧引用,这样旧引用对象就会被标记到,也就是上面说的 Anything live at Initial Marking is considered live.

  2  Anything allocated since Initial Marking is considered live.

  Region包含了5个指针,分别是bottom、previous TAMS、next TAMS、top和end

  

    

  其中previous TAMS、next TAMS是前后两次发生并发标记时的位置,全称top-at-mark-start
  1、假设第n轮并发标记开始,将该Region当前的top指针赋值给next TAMS,在并发标记标记期间,分配的对象都在[next TAMS, top]之间,SATB能够确保这部分的对象都会被标记,默认都是存活的
  2、当并发标记结束时,将next TAMS所在的地址赋值给previous TAMS,SATB给 [bottom, previous TAMS] 之间的对象创建一个快照Bitmap,所有垃圾对象能通过快照被识别出来
  3、第n+1轮并发标记开始,过程和第n轮一样
  
  这样在remark阶段,就能保证在并发收集线程里保存的就引用值不会漏标,新增的那些对象也不会漏标
 
===============================2021-10-25更新===============================

SATB详细流程

  1. SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对像就认为是活的,从而开成一个对象图。

  2. 在GC收集的时候,新生代的对象也认为是活的对象,除此之外其他不可达的对象都认为是垃圾对象。

  3. 如何找到在GC过程中分配的对象呢?每个region记录着两个top-at-mark-start(TAMS)指针,分别为prevTAMS和nextTAMS。在TAMS以上的对象就是新分配的,因而被视为隐式marked。

  4. 通过这种方式我们就找到了在GC过程中新分配的对象,并把这些对象认为是活的对象。

  5. 解决了对象在GC过程中分配的问题,那么在GC过程中引用发生变化的问题怎么解决呢?

  6. G1给出的解决办法是通过Write Barrier。Write Barrier就是对引用字段进行赋值做了额外处理。通过Write Barrier就可以了解到哪些引用对象发生了什么样的变化。

  7. mark的过程就是遍历heap标记live object的过程,采用的是三色标记算法,这三种颜色为white(表示还未访问到)、gray(访问到但是它用到的引用还没有完全扫描)、back(访问到而且其用到的引用已经完全扫描完)。

  8. 整个三色标记算法就是从GC roots出发遍历heap,针对可达对象先标记white为gray,然后再标记gray为black;遍历完成之后所有可达对象都是balck的,所有white都是可以回收的。

  9. SATB仅仅对于在marking开始阶段进行“snapshot”(marked all reachable at mark start),但是concurrent的时候并发修改可能造成对象漏标记。并非所有对象,而是开始阶段的存活对象

  10. 对black新引用了一个white对象,然后又从gray对象中删除了对该white对象的引用,这样会造成了该white对象漏标记。

  11. 对black新引用了一个white对象,然后从gray对象删了一个引用该white对象的white对象,这样也会造成了该white对象漏标记。

  12. 对black新引用了一个刚new出来的white对象,没有其他gray对象引用该white对象,这样也会造成了该white对象漏标记。

       总结:
  1 G1标记分为快照部分和增量部分。
  2 快照部分是初始标记的时候做快照,哪些对象是活的就让它活过本轮GC。增量部分通过写屏障记录到RSET中,在最终标记的时候只扫描RSET。
  3 增量部分还包括新增的对象,如果表示新增对象。每个Region都有分配对象的指针,TAMS top-at-mark-start(TAMS)。在两个TAMS指针之间的就是新增对象
  

  

posted on 2020-10-27 10:56  MaXianZhe  阅读(1018)  评论(0编辑  收藏  举报

导航