GC 三色标记算法(Go Java版本)

一、前言

GC全称Garbage Collection,目前主流的垃圾回收算法有两类,分别是追踪式垃圾回收算法(Tracing garbage collection)和引用计数法( Reference counting )。

而三色标记法是属于追踪式垃圾回收算法的一种。追踪式算法的核心思想是判断一个对象是否可达,因为一旦这个对象不可达就可以立刻被 GC 回收了。

那如何判断一个对象是否可达呢?

在go推出三色标记法之前,Go 所使用的gc算法叫Mark-And-Sweep(标记清除)。这个算法就是严格按照追踪式算法的思路来实现的。

1.1 标记清除算法

标记清除(Mark-Sweep)算法是最常见的垃圾收集算法,标记清除收集器是跟踪式垃圾收集器,其执行过程可以分成标记(Mark)和清除(Sweep)两个阶段:

  • 标记阶段 — 从根对象出发查找并标记堆中所有存活的对象;
  • 清除阶段 — 遍历堆中的全部对象,回收未被标记的垃圾对象并将回收的内存加入空闲链表;

如下图所示,内存空间中包含多个对象,我们从根对象出发依次遍历对象的子对象并将从根节点可达的对象都标记成存活状态,即 A、C 和 D 三个对象,剩余的 B、E 和 F 三个对象因为从根节点不可达,所以会被当做垃圾:
在这里插入图片描述
标记阶段结束后会进入清除阶段,在该阶段中收集器会依次遍历堆中的所有对象,释放其中没有被标记的 B、E 和 F 三个对象并将新的空闲内存空间以链表的结构串联起来,方便内存分配器的使用。在这里插入图片描述
这里介绍的是最传统的标记清除算法,垃圾收集器从垃圾收集的根对象出发,递归遍历这些对象指向的子对象并将所有可达的对象标记成存活;标记阶段结束后,垃圾收集器会依次遍历堆中的对象并清除其中的垃圾,整个过程需要标记对象的存活状态,用户程序在垃圾收集的过程中也不能执行,我们需要用到更复杂的机制(三色标记法)来解决 STW 的问题。

1.2 垃圾收集的执行周期

CMS 收集器是基于标记-清除算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为四个步骤,包括:

1)初始标记(CMS initial mark) 暂停所有的其他线程(STW)。记录下 GC ROOT 直接引用对象,速度很快。

2)并发标记(CMS concurrent mark) 并发标记阶段就是从 GC ROOT 行的直接关联对象开始遍历整个对象的过程,这个过程耗时比较长但是不需要停顿用户线程,可以与垃圾收集器一起并发运行。因此用户程序继续运行,可能会导致已经标记过的对象状态发生变化。

3)重新标记(CMS remark) 重新标记阶段就是为了修正并发标记期间,因为用户程序继续运行而导致标记产生变动,的那一部分对象的标记记录。这个阶段的停顿时间一般比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要是用到三色标记里的增量更新算法

4)并发清除(CMS concurrent sweep)开启用户线程,同时 GC 线程开始对未标记的区域做清扫,这个阶段如果有新增对象会被标记为黑色不做任何处理(见下面三色标记算法详解)。

三色标记法是传统 Mark-Sweep 的一个改进,它是一个并发的 GC 算法。on-the-fly

二、什么是三色标记法

为了解决原始标记清除算法带来的长时间 STW,多数现代的追踪式垃圾收集器都会实现三色标记算法的变种以缩短 STW 的时间。

主流的垃圾收集器基本上都是基于可达性分析算法来判定对象是否存活的。根据对象是否被垃圾收集器扫描过而用白、灰、黑三种颜色来标记对象的状态的一种方法。而其中

  • 白色对象 —表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始阶段,所有的对象都是白色的,若在分析结束之后对象仍然为白色,则表示这些对象为不可达对象,对这些对象进行回收。
  • 黑色对象 —表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经被扫描过。黑色表示这个对象扫描之后依然存活,是可达性对象,如果有其他对象引用指向了黑色对象,无须重新扫描,黑色对象不可能不经过灰色对象直接指向某个白色对象。
  • 灰色对象 — 表示对象已经被垃圾收集器访问过,但是这个对象至少存在一个引用(属性)还没有被扫描过。
    在这里插入图片描述

三、三色标记的过程

从我们main方法的根对象(JVM中称为GC Root)开始沿着他们的对象向下查找,用黑灰白的规则,标记出所有跟GC Root相连接的对象(不递归),扫描一遍结束后,一般需要进行一次短暂的STW(Stop The World)。

只需找出灰色对象并顺着继续往下标记(且因为大部分的标记工作已经在第一次并发的时候发生了,所以灰色对象数量会很少,标记时间也会短很多), 此时程序继续执行,GC线程扫描所有的内存,找出扫描之后依旧被标记为白色的对象(垃圾)并清除。

3.1 标记的原理

整个进程空间里申请每个对象占据的内存可以视为一个图, 初始状态下每个内存对象都是白色标记。

第一轮:先stop the world,将扫描任务作为多个并发的goroutine立即入队给调度器,进而被CPU处理,第一轮先扫描所有可达的内存对象,标记为灰色放入队列。

第二轮:可以恢复start the world,将第一步队列中引用的对象置为灰色加入队列,一个对象引用的所有对象都置灰并加入队列后,这个对象才能置为黑色并从队列之中取出。循环往复,最后队列为空时,整个图剩下的白色内存空间即不可到达的对象,即没有被引用的对象;

第三轮:再次stop the world,将第二轮过程中新增对象申请的内存进行标记(灰色),这里使用了writebarrier(写屏障)去记录这些内存的身份;

这个算法可以实现 on-the-fly(即时),也就是在程序执行的同时进行收集,并不需要暂停整个程序。

标记的过程

3.2 标记的步骤

(1)首先创建三个集合:白、灰、黑。
在这里插入图片描述
(2)将所有对象放入白色集合中。
在这里插入图片描述
(3)然后从根节点开始遍历所有对象(注意这里并不递归遍历),把遍历到的对象从白色集合放入灰色集合。因为root set 指向了A、F,所以从根结点开始遍历的是A、F,所以是把A、F放到灰色集合中。
在这里插入图片描述
(4)之后遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合,我们可以发现这个A指向了B,C,D所以也就是把BCD放到灰色中,把A放到黑色中,而F没有指任何的对象,所以直接放到黑色中。
在这里插入图片描述
(5)重复 4 直到灰色中无任何对象,因为D指向了A所以D也放到了黑色中,而B和C能放到黑色集合中的道理和F一样,已经没有了可指向的对象了。
在这里插入图片描述
(6)通过write-barrier检测对象有无变化,重复以上操作,由于这个EGH并没有和RootSet有直接或是间接的关系,所以就会被清除。
在这里插入图片描述
(7)收集所有白色对象(垃圾)
在这里插入图片描述
所以我们可以看出这里的情况,只要是和root set根集合直接相关的对象或是间接相关的对象都不会被清楚。只有不相关的才会被回收。

四、标记存在的问题—多标和漏标

垃圾收集器在并发标记的过程中,执行标记期间应用线程还在并行运行,对象间的引用关系时刻发生变化,垃圾收集器在标记过程中就容易发生多标和漏标(其实多标和漏标我们统称为误标)。

三色标记示例

public class TriColorMarking {

    public static void main(String[] args) {
        A a = new A();
        //开始做并发标记
        D d = a.b.d;   // 1.读
        a.b.d = null;  // 2.写
        a.d = d;       // 3.写
    }
}

class A {
    B b = new B();
    D d = null;
}

class B {
    C c = new C();
    D d = new D();
}

class C {
}

class D {
}

例子的一个简单说明:

  1. 在 new A() 的时候会创建引用关系 A -> B ,B-> C , B -> D;
  2. 当我们做并发标记的时候,垃圾收集器访问过 A、B、C、D 最终都标记为黑色。但是这个时候程序执行了一个 a.b.d = null 就标识 D 其实是没有引用,理论上 D 对象可以被回收。这种情况就产生了 “浮动垃圾”。
  3. 当我们发现了 D 没有引用,标记为白色,但是在标记完成过后发现 a.d = d 。又新增了对象引用如果将 d 回收掉程序就会报错肯定是不行的。这是一个典型的 “多标” 场景。

下面我们会通过并发标记的过程中出现的漏标和多标场景进行分析。

4.1 漏标

在并发标记过程中,将原本消亡的对象标记为存活对象,这就是漏标。就会产生浮动垃圾,需要等到下次 GC 的时候清理。

产生过程:程序删除了全部从灰色对象到该白色对象的直接或者间接引用。
在这里插入图片描述
其实浮动垃圾是可以接受的只会影响垃圾收集器的效率,或者说是收集的比率。

4.2 多标

在并发标记过程中,将原本存活的对象标记为需要回收的对象。

产生过程:程序插入一条或者多条从黑色对象到白色对象的新引用。
在这里插入图片描述
这种情况是不可以接受的,如果正在被使用的程序对象被 JVM 回收,会导致程序运行错误,是不可以接受的会导致严重 BUG。

五、解决漏标和多标的方案

解决漏标和多标分别有两种解决方案:增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning, STAB)。

  • 增量更新(Incremental Update):在并发标记过程中,当黑色对象插入了新的指向白色引用关系时,就将这个插入引用记录下来,并发标记结束后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。简化理解, 黑色对象一旦新插入了指向白色对象的引用之后, 它就变成灰色对象
  • 原始快照(Snapshot At The Beginning, STAB):这并发标记过程中,当灰色对象要删除白色对象的引用关系时,就将这个需要删除的记录下来,在并发扫描结束后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次,这样就能扫描到白色对象,将白色的对象直接标记为黑色(目的就是为了让这种对象在本轮 GC 清理中能够存活下来,待下一轮 GC 的时候重新扫描,这个对象也可能成为浮动垃圾) 总之,无论是引用关系记录插入还是删除,虚拟机的记录操作都是通过写屏障来实现的。

5.1 写屏障(Write Barrier)

JVM 通过写屏障(Write Barrier)来维护卡表,卡表是记忆集的实现。记忆集是用来缩小 GC Root 的扫描范围,我们在 GC 的时候只需要去过滤卡表变脏(Dirty)的元素,找到具体一块卡页内存块,放入 GC Root 中一块扫描。这是大概的一个流程,后续会讲到,先有一个印象。再回到写屏障,下面是一个对象赋值操作:

/**
* @param field 某对象的成员变量,如 a.b.d 
* @param new_value 新值,如 null
*/
void oop_field_store(oop* field, oop new_value) { 
    *field = new_value; // 赋值操作
}

写屏障可以看做是虚拟机执行对象字段赋值的一个拦截,类比 Spring AOP 的切面思想。

void oop_field_store(oop* field, oop new_value) {  
    pre_write_barrier(field);          // 写前屏障
    *field = new_value; 
    post_write_barrier(field, value);  // 写后屏障
}

5.1.1 写屏障之STAB

当对象B的成员变量的引用发生变化时,比如引用消失(a.b.d = null),我们可以利用写屏障,将B原来成员变量的引用对象D记录下来:

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

5.1.2 写屏障之增量更新

当对象A的成员变量的引用发生变化时,比如新增引用(a.d = d),我们可以利用写屏障,将A新的成员变量引用对象D记录下来。

void post_write_barrier(oop* field, oop new_value) {  
    remark_set.add(new_value);  // 记录新引用的对象
}

5.2 读屏障(Load Barrier)

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

读屏障是直接针对第一步:D d = a.b.d,当读取成员变量时,一律记录下来:

void pre_load_barrier(oop* field) {  
    oop old_value = *field;
    remark_set.add(old_value); // 记录读取到的对象
}

5.3 记忆集和卡表(Remembered Set And Card Table)

垃圾收集器在新生代建立了记忆集(Remembered Set)的数据结构,用来避免把整个老年代的 GC root 扫描一遍。事实上并不只是新生代、 老年代之间才有跨代引用的问题, 所有涉及部分区域收集(Partial GC) 行为的垃圾收集器, 典型的如G1、 ZGC 和 Shenandoah 收集器, 都会面临相同的问题。记忆集是一种记录非收集区域指向收集区域的指针集合抽象的数据结构

Hotspot 中使用一种叫做 “卡表” (Card Table)的方式来实现记忆集,也是目前最常用的一种方式。卡表和记忆集的关系,可以类比 Java 语言中 HashMap 和 Map 之间的关系。卡表是一个字节数组实现:CARD_TABLE[], 每个元素都对应着一个标识的内存区域一块特定大小的内存块,称为“卡页”。Hotsport 卡页的大小是 2^9 也就是 512 字节。
在这里插入图片描述
一个卡页中可以包含多个对象,只要卡页内一个或者多个对象的字段存在跨代引用,其对应的卡表的元素标识就变成了1,表示该元素变脏,否则为 0。GC 时,只需要筛选卡表中变脏的元素加入到 GCRoot 中。

卡表的维护
如何让卡表变脏,即发生引用字段赋值时,如何更新卡表对应的标识为 1。Hotspot使用写屏障维护卡表状态。

5.4 收集器采用的解决方案

  • CMS : 写屏障,增量更新
  • G1,Shednandoah: 写屏障 + STAB
  • ZGC:读屏障

5.4.1 为什么 G1 采用 SATB,CMS 使用增量更新?

因为SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象。

而CMS对增量更新的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC再深度扫描。

六、总结

垃圾收集器的实现非常复杂,我们在分析垃圾收集器的过程中不得不省略很多的实现细节,其中包括并发标记对象的过程、清扫垃圾的具体实现,这些过程设计大量底层的位操作和指针操作。

垃圾收集是一门非常古老的技术,它的执行速度和利用率很大程度上决定了程序的运行速度,其中Go 语言为了实现高性能的并发垃圾收集器,使用三色抽象、并发增量回收、混合写屏障、调度算法以及用户程序协助等机制将垃圾收集的暂停时间优化至毫秒级以下,从早期的版本看到今天,我们能体会到其中的工程设计和演进。

相关材料

  1. JVM 从入门到放弃之 Java 对象创建过程
  2. JVM 垃圾回收算法和 CMS 垃圾回收器
  3. JVM 从入门到放弃之 ZGC 垃圾收集器
  4. JVM 运行时内存分代结构
  5. JVM 字节码解析过程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/22704.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

我出版了一本关于TikTok电商运营的书

回首2020年初,第一次在手机上下载TikTok的那个下午,我并没有意识到,未来三年多这个词会充满我的工作与生活。 那其实是非常幸福的一段时间,对TikTok的期待没有那么功利,每天刷一刷TikTok中的视频,再随手拍…

车辆合格证怎么转为结构化excel数据?

一、为何要将车辆合格证转为结构化excel? 车辆合格证是在车辆制造完成后,经过各项检测合格的证明。对于车辆行业来说,车辆合格证是一种重要的合规证明,在车辆的生产制造、售后服务、质量管理等各个环节中都有着重要的作用。同时&…

git pull报没有足够内存 not enough memory for initialization

git clone 或 git pull 批量同步远程 git仓库代码时,报 没有足够内存用于初始化 not enough memory for initialization。经过观察 资源管理器 的内存使用情况,发现为 剩余可用内存不足造成的。加物理内存麻烦,可通过适当调整 分页文件&…

软考知识点---08IP地址与域名地址

📢博客主页:盾山狂热粉的博客_CSDN博客-C、C语言,机器视觉领域博主📢努力努力再努力嗷~~~✨ 一、IP地址 (一)什么是IP地址? 连入互联网的计算机,每台计算机或者路由器都有一个由授权机构分配的…

煤矿电子封条实施方案 yolov7

煤矿电子封条实施方案采用YOLOv7网络模型算法技术,煤矿电子封条实施算法模型过将全国各省矿山实时监测数据,实现对全国各矿山及时有效的处理及分析。YOLOv7 的发展方向与当前主流的实时目标检测器不同,研究团队希望它能够同时支持移动 GPU 和…

零入门kubernetes网络实战-33->基于nat+brigde+veth pair形成的跨主机的内网通信方案

《零入门kubernetes网络实战》视频专栏地址 https://www.ixigua.com/7193641905282875942 本篇文章视频地址(稍后上传) 本文主要使用的技术是 nat技术Linux虚拟网桥虚拟网络设备veth pair来实现跨主机网桥的通信 1、测试环境介绍 两台centos虚拟机 # 查看操作系统版本 cat …

Unity3D安装:从命令行安装 Unity

推荐:将 NSDT场景编辑器 加入你的3D工具链 3D工具集: NSDT简石数字孪生 从命令行安装 Unity 如果要在组织中自动部署 Unity,可以从命令行安装 Editor 和其他组件。这些组件是普通的安装程序可执行程序和软件包,可以给用来自动部署…

圣墟传说H5手工端搭建架设教程

圣墟传说H5手工端搭建架设教程 大家好,我是艾西。今天给大家带来的游戏是由小说改编而来的大型玄幻MMORPG仙侠手游,也是比较老的游戏了虽然你可能没有怎么听过,但总会有一批喜欢的玩家热衷于它。 那么让我们直接进入正题开始操作&#xff1…

【状态估计】电力系统状态估计的虚假数据注入攻击建模与对策(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

云原生|Kubernetes Operator测试实例

目录 一、主要代码介绍 (一)变量定义: (二)测试程序入口 (三)before函数 (四)after函数 二、实际测试 (一)块划分 (二&#x…

原神服务端搭建架设Centos系统

原神服务端搭建架设Centos系统 我是艾西,今天为大家带来原神服务端centos系统的教程 Step1. 准备工具 这个端在Windows、Linux系统上都可以跑,本次教程基于Linux。 准备如下工具: 服务器1台 centos7 系统 最低配置32核32G 公网联机 2. 手…

动态规划问题实验:数塔问题

目录 前言实验内容实验流程实验过程实验分析伪代码代码实现分析算法复杂度用例测试 总结 前言 动态规划是一种解决复杂问题的方法,它将一个问题分解为若干个子问题,然后从最简单的子问题开始求解,逐步推导出更复杂的子问题的解,最…

设计原则-单一职责原则

在编程大环境中,评价代码组织方式质量的好坏涉及到各个方面,如代码的可读性、可维护性、可复用性、稳定性等各个方面。而在面向对象语言中也可以通过以下各个方面: 类中方法的设计类中属性的设计类(接口、抽象类、普通类)的设计类与类之间的…

十万条数据,后端不分页咋办!(如何优化长列表渲染)

十万条数据,后端不分页咋办!(如何优化长列表渲染) 长列表是什么? 我们通常把一组数量级很大的数据叫做长列表,比如渲染一组上千条的数据,我们以数组的形式拿到这些信息,然后遍历渲…

正点原子ALPHA开发板核心资源分析

目录 正点原子ALPHA开发板核心资源分析I.MX6ULL实物图对比SOC 主控芯片(MCIMX6Y2CVM08AB)NAND FLASHEMMCDDR3L 正点原子ALPHA开发板核心资源分析 I.MX6ULL实物图对比 I.MX6ULL NAND BTB 接口核心板资源图与 I.MX6ULL EMMC BTB 接口核心板资源图如上图&a…

电商项目9:新增商品

电商项目9:新增商品 1、前端1.1、修复前端组件通信问题1.2、引入其他前端代码1.3、会员等级列表1.4、当前分类关联的所有品牌 2、后端2.1、会员系统搭建(注册与发现)2.2、当前分类关联的所有品牌2.3、获取分类下所有分组&关联属性 1、前端…

shell sed命令

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 sed 命令sed 编辑器sed 的工作流程的三个过程命定格式常用选项常用操作 实验操作打印内容使用地址删除行替换插入 sed 命令 sed 编辑器 sed是一种流编辑器&#x…

听我一句劝,别去外包,干了6年,废了....

先说一下自己的情况,大专生,18年通过校招进入湖南某软件公司,干了接近6年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了6年的功能测试&…

中国移动董宁:深耕区块链的第八年,我仍期待挑战丨对话MVP

区块链技术对于多数人来说还是“新鲜”的代名词时,董宁已经成为这项技术的老朋友。 董宁2015年进入区块链领域,现任中国移动研究院技术总监、区块链首席专家。作为“老友”,董宁见证了区块链技术多个爆发式增长和平稳发展的阶段,…

Doxygen 源码分析: SymbolMap类

2023-05-21 10:59:35 ChrisZZ imzhuofoxmailcom Hompage https://github.com/zchrissirhcz 文章目录 1. Doxygen 版本2. SymbolMap 类概要3. 添加符号: SymbolMap<T>::add()4. 删除符号: SymbolMap<T>::remove()5. 符号查找: SymbolMap<T>::find()6. 哪里用了…