Panda白话 - G1垃圾收集器 - SATB、写屏障

Panda白话 - G1垃圾收集器 - 三色标记法、漏标、增量更新、SATB
上文讲到G1在并发标记过程中,通过三色标记法来标记存活对象, 三色标记法存在漏标问题,会影响程序正确性, G1采用SATB方案解决漏标问题,本文我们就来重点分析一下SATB

SATB - Snapshot At The Beginning 初始快照

  • 由Taiichi Yuasa开发的一个算法 - 首先它是一种思想
  • 主要用于GC的并发标记阶段
  • 记录并发标记是mutator修改的引用记录,remark阶段(STW)无需全量重新扫描标记
  • 快照是以BitMap(位图)的方式实现,BitMap存放对象存活标记,1 为黑色 即存活
    在这里插入图片描述

见上图、Region包含了5个指针:

  • Bottom - 总是指向Region的起始位置
  • Previous TAMS - 指向上一次并发处理后的地址
  • Next TAMS - 指向并发标记开始之前内存已经分配成功的地址
  • Yop - 指向当前内存分配成功的地址
  • End - 总是指向Region的终点位置

TAMS - top-at-mark-start
对象在Region中是连续分配的

可以知道:

  • [Bottom,End]区间就是Region内存空间大小
  • [Bottom,Prev]区间就是上次并发标记结束后,已经标记过的对象内存
  • [Prev,Next]区间就是本次并发标记标记到的对象内存
  • [Next,Top]区间就是并发标记过程中Mutator新增的对象分配的内存地址
  • [Top,End] 区间就是Region还未分配的空闲内存

由上可知,Prev和Next指针解决了并发标记中内存区域问题,并发标记引入了两个**数据结构**来记录内存标记状态,

  • PrevBitMap - 记录Prev指针之前内存标记情况、即【Bottom,Prev】区间
  • NextBitMap - 记录Next指针之前内存标记情况、即【Bottom,Next】区间
    图解两次并发标记过程:
    在这里插入图片描述

第一次并发标记开始前 :

  • PrevBitMap 为空,NextBitMap 待标记
    在这里插入图片描述

第一次标记结束后:

  • NextBitMap 标记了分区对象存活情况
  • NextBitMap位图中黑色区域表示对应对象存活
  • 并发标记过程中Mutator继续运行,产生新的对象,Top指针继续增长
    -

第二次并发标记开始前:

  • 重置指针,Prev指针指向Next指针位置
  • 交换位图,PrevBitMap获取NextBitMap记录,NextBitMap 清空
    -

第二次并发标记结束后:

  • Next指针指向标记前已分配内存顶部,即Top指针位置,即完成上次标记时新增对象【Next,Top】区间的扫描标记
  • NextBitMap记录所有已扫描对象内存标记状态
  • Top指针持续增长
    在这里插入图片描述

最终标记-Remark开始之前:
同上
在这里插入图片描述

最终标记-Remark结束之后:

  • Remark阶段STW,Mutator暂停执行,Top不会继续增长
  • Prev指向Next
  • Next 和Top都指向了已分配对象顶部
  • NextBitMap 记录所有对象标记情况
    在这里插入图片描述上面步骤可以看出:
  • 每次并发标记后,将本次标记结果【Bottom,Prev】区间做了一次Snapshot快照,以BitMap位图存储,所有垃圾对象通过快照被识别出来
  • 并发标记中Mutator新增的对象都认为是存活对象,设置为灰色,因为SATB关注的是引用的删除,将 o1.filed = new O2() 修改为o1.field = null 这种,会将O2对象置为灰色,加入操作栈,重新进行扫描,解决漏标问题

写屏障:

SATB记录的是删除引用的对象,通过写屏障来实现
写屏障的代码在 oop.inline.hpp

template <class T> inline void oop_store(T* p, oop v) {
  if (always_do_update_barrier) {
    oop_store((volatile T*)p, v);
  } else {
  	//引用赋值前屏障
    update_barrier_set_pre(p, v);
    //引用赋值
    oopDesc::encode_store_heap_oop(p, v);
    //引用赋值后屏障 
    update_barrier_set((void*)p, v, false /* release */);
  }
}

写屏障总结为:

JVM ----> Insert Pre-write barrier // 插入写前屏障
Object.Field = other_object; //引用赋值
JVM ----> Insert Post-write barrier //插入写后屏障

G1 使用的是写前屏障Pre-write barrier,关注引用的删除,
写前屏障最终执行的代码在:

void G1SATBCardTableModRefBS::enqueue(oop pre_val) {
  if (!JavaThread::satb_mark_queue_set().is_active()) return;
  Thread* thr = Thread::current();
  if (thr->is_Java_thread()) {
    JavaThread* jt = (JavaThread*)thr;
    //将旧引用保存到satb_mark_queue队列
    jt->satb_mark_queue().enqueue(pre_val);
  } else {
  //本地代码则放入全局共享队列,需上锁
    MutexLockerEx x(Shared_SATB_Q_lock,Mutex::_no_safepoint_check_flag);
    JavaThread::satb_mark_queue_set().shared_satb_queue()->enqueue(pre_val);
  }
}

分析:

  • 将旧引用保存到satb_mark_queue
  • 下次并发标记会以此处理satb_mark_queue中的对象
  • 失去引用的对象在本轮GC是存活的
  • 白色对象会成为浮动垃圾-float garbage
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值