JVM 三色标记法与读写屏障

JVM 三色标记法与读写屏障

作者: 老郑 2021-08-16 10:35:52

云计算

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

本文转载自微信公众号「运维开发故事」,作者老郑。转载本文请联系运维开发故事公众号。

三色标记法

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

三色标记法简介

三色标记法,主要是为了高效的标记可被回收的内存块。

三色标记(Tri-color Marking)作为工具来辅助推导,把遍历对象图过程中遇到的对象,按照“是否访问过”这个条件标记成以下三种颜色:

  • 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
  • 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代 表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对 象不可能直接(不经过灰色对象)指向某个白色对象。
  • 灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。

三色标记过程

标记过程:

  1. 在 GC 并发开始的时候,所有的对象均为白色;
  2. 在将所有的 GC Roots 直接应用的对象标记为灰色集合;
  3. 如果判断灰色集合中的对象不存在子引用,则将其放入黑色集合,若存在子引用对象,则将其所有的子引用对象存放到灰色集合,当前对象放入灰色集合
  4. 按照此步骤 3 ,依此类推,直至灰色集合中所有的对象变黑后,本轮标记完成,并且在白色集合内的对象称为不可达对象,即垃圾对象。
  5. 标记结束后,为白色的对象为 GC Roots 不可达,可以进行垃圾回收。

误标

什么是误标?当下面两个条件同时满足,会产生误标:

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

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

误标的解决方案

要解决误标的问题,只需要破坏这两个条件中的任意一种即可,分别有两种解决方案:增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning, STAB)

增量更新

增量更新要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象 了。

原始快照 (STAB)

原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删 除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描 一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。

漏标和多标

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

多标-浮动垃圾

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

在这个时候, E/F/G 理论上是可以被回收的。但是由于 E 已经变为了灰色了,那么它就会继续执行下去。最终的结果就是不会将他们标记为垃圾对象,在本轮标记中存活。在本轮应该被回收的垃圾没有被回收,这部分被称为“浮动垃圾”。浮动垃圾并不会影响程序的正确性,这些“垃圾”只有在下次垃圾回收触发的时候被清理。还有在,标记过程中产生的新对象,默认被标记为黑色,但是可能在标记过程中变为“垃圾”。这也算是浮动垃圾的一部分。

漏标-读写屏障

写屏障(Store Barrier)

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

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

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

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

写屏障 + SATB

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

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

【当原来成员变量的引用发生变化之前,记录下原来的引用对象】 这种做法的思路是:尝试保留开始时的对象图,即原始快照(Snapshot At The Beginning,SATB),当某个时刻 的GC Roots确定后,当时的对象图就已经确定了。比如 当时 D是引用着G的,那后续的标记也应该是按照这个时刻的对象图走(D引用着G)。如果期间发生变化,则可以记录起来,保证标记依然按照原本的视图来。值得一提的是,扫描所有GC Roots 这个操作(即初始标记)通常是需要STW的,否则有可能永远都扫不完,因为并发期间可能增加新的GC Roots。

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

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

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

写屏障 + 增量更新

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

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

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

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

读屏障(Load Barrier)

  1. oop oop_field_load(oop* field) { 
  2.     pre_load_barrier(field); // 读屏障-读取前操作 
  3.     return *field; 

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

  1. void pre_load_barrier(oop* field, oop old_value) {   
  2.   if($gc_phase == GC_CONCURRENT_MARK && !isMarkd(field)) { 
  3.       oop old_value = *field; 
  4.       remark_set.add(old_value); // 记录读取到的对象 
  5.   } 
  6. 这种做法是保守的 

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

三色标记法与垃圾回收器

增量更新:CMS

原始快照(STAB):G1,Shenandoah

参考文档

https://www.jianshu.com/p/12544c0ad5c1

https://hllvm-group.iteye.com/group/topic/44381

https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html

https://tech.meituan.com/2016/09/23/g1.html

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

 

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/239909.html<

(0)
运维的头像运维
上一篇2025-04-23 15:16
下一篇 2025-04-23 15:17

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注