JVM 三色标记法与读写屏障(下)

简介: GC 垃圾回收器其主要的目的是为了实现内存的回收,在这个过程中主要的两个步骤就是:内存标记,内存回收。

漏标和多标


对于错标其实细分出来会有两种情况,分别是:漏标和多标


多标-浮动垃圾


如果标记执行到 E 此刻执行了 object.E = null


image.png


在这个时候, E/F/G 理论上是可以被回收的。但是由于 E 已经变为了灰色了,那么它就会继续执行下去。最终的结果就是不会将他们标记为垃圾对象,在本轮标记中存活。


在本轮应该被回收的垃圾没有被回收,这部分被称为“浮动垃圾”。浮动垃圾并不会影响程序的正确性,这些“垃圾”只有在下次垃圾回收触发的时候被清理。


还有在,标记过程中产生的新对象,默认被标记为黑色,但是可能在标记过程中变为“垃圾”。这也算是浮动垃圾的一部分。


漏标-读写屏障


写屏障(Store Barrier)


给某个对象的成员变量赋值时,其底层代码大概长这样:


/**
 * @param field 某个对象的成员属性
 * @param new_value 新值,如:null
 */
void oop_field_store(oop* field, oop new_value) {
    *fieild = new_value // 赋值操作
}


所谓写屏障,其实就是在赋值操作前后,加入一些处理的逻辑(类似 AOP 的方式)


void oop_field_store(oop* field, oop new_value) {
    pre_write_barrier(field); // 写屏障-写前屏障
    *fieild = new_value // 赋值操作 
    pre_write_barrier(field); // 写屏障-写后屏障
}


写屏障 + SATB


当对象E的成员变量的引用发生变化时(objE.fieldG = null;),我们可以利用写屏障,将E原来成员变量的引用对象G记录下来:


void pre_write_barrier(oop* field) {
    oop old_value = *field; // 获取旧值
    remark_set.add(old_value); // 记录 原来的引用对象
}


【当原来成员变量的引用发生变化之前,记录下原来的引用对象


这种做法的思路是:尝试保留开始时的对象图,即原始快照(Snapshot At The Beginning,SATB) ,当某个时刻 的GC Roots确定后,当时的对象图就已经确定了。


比如 当时 D是引用着G的,那后续的标记也应该是按照这个时刻的对象图走(D引用着G)。如果期间发生变化,则可以记录起来,保证标记依然按照原本的视图来。


值得一提的是,扫描所有GC Roots 这个操作(即初始标记)通常是需要STW的,否则有可能永远都扫不完,因为并发期间可能增加新的GC Roots。


SATB破坏了条件一:【灰色对象 断开了 白色对象的引用】,从而保证了不会漏标。


一点小优化:如果不是处于垃圾回收的并发标记阶段,或者已经被标记过了,其实是没必要再记录了,所以可以加个简单的判断:


void pre_write_barrier(oop* field) {
  // 处于GC并发标记阶段 且 该对象没有被标记(访问)过
  if($gc_phase == GC_CONCURRENT_MARK && !isMarkd(field)) { 
      oop old_value = *field; // 获取旧值
      remark_set.add(old_value); // 记录  原来的引用对象
  }
}


写屏障 + 增量更新


当对象D的成员变量的引用发生变化时(objD.fieldG = G;),我们可以利用写屏障,将D新的成员变量引用对象G记录下来:


void post_write_barrier(oop* field, oop new_value) {  
  if($gc_phase == GC_CONCURRENT_MARK && !isMarkd(field)) {
      remark_set.add(new_value); // 记录新引用的对象
  }
}


当有新引用插入进来时,记录下新的引用对象


这种做法的思路是:不要求保留原始快照,而是针对新增的引用,将其记录下来等待遍历,即增量更新(Incremental Update)。


增量更新破坏了条件二:【黑色对象 重新引用了 该白色对象】,从而保证了不会漏标。


读屏障(Load Barrier)


oop oop_field_load(oop* field) {
    pre_load_barrier(field); // 读屏障-读取前操作
    return *field;
}


读屏障直接针对第一步 var objF = object.fieldG;,


void pre_load_barrier(oop* field, oop old_value) {  
  if($gc_phase == GC_CONCURRENT_MARK && !isMarkd(field)) {
      oop old_value = *field;
      remark_set.add(old_value); // 记录读取到的对象
  }
}


这种做法是保守的,但也是安全的。因为条件二中【黑色对象 重新引用了 该白色对象】,重新引用的前提是:得获取到该白色对象,此时已经读屏障就发挥作用了。


三色标记法与垃圾回收器


增量更新: CMS原始快照(STAB): G1,Shenandoah


参考文档






  • 《深入理解 JVM 虚拟机-第三版》周志明



相关文章
|
11天前
|
算法 Java
深入浅出JVM(十六)之三色标记法与并发可达性分析
深入浅出JVM(十六)之三色标记法与并发可达性分析
|
11天前
|
存储 算法 安全
JVM-并发标记带来问题和解决办法
JVM-并发标记带来问题和解决办法
45 0
|
8月前
|
算法 安全 Java
阿里二面:JVM 的三色标记算法你了解吗?
阿里二面:JVM 的三色标记算法你了解吗?
|
10月前
|
算法 Java
25-【扩展补充】JVM 三色标记 增量更新 原始快照
本文将介绍JVM中的三色标记算法、增量更新和原始快照的概念。 首先,我们将深入探讨JVM中的三色标记算法。这种垃圾回收算法基于可达性分析,将对象分为三个状态:白色、灰色和黑色。通过标记对象的可达性,垃圾回收器可以确定哪些对象可以安全地回收,从而有效地管理内存。 接下来,我们将介绍增量更新技术。增量更新是一种垃圾回收的优化方法,它将垃圾回收过程分为多个阶段,并与应用程序交替执行。通过这种方式,增量更新可以减少垃圾回收的停顿时间,提高应用程序的响应性能。
132 0
|
10月前
|
存储 人工智能 缓存
jvm之垃圾回收标记相关算法解读
jvm之垃圾回收标记相关算法解读
|
11月前
|
算法 安全 Java
你对JVM三色标记的理解嘛?
你对JVM三色标记的理解嘛?
85 0
你对JVM三色标记的理解嘛?
|
12月前
|
算法 安全 Java
JVM 三色标记法
JVM 三色标记法
|
11天前
|
存储 缓存 算法
深入浅出JVM(二)之运行时数据区和内存溢出异常
深入浅出JVM(二)之运行时数据区和内存溢出异常
|
1天前
|
存储 Java 对象存储
JVM(内存区域划分)
JVM(内存区域划分)
10 1
|
11天前
|
Java Linux Arthas
linux上如何排查JVM内存过高?
linux上如何排查JVM内存过高?
830 0