一文搞定垃圾回收的三色标记法

news2024/9/24 17:13:42

我们之前介绍了各种常见垃圾回收器的基本原理,本小节我们讨论一个更深入的问题——垃圾回收器的底层是如何做的。

在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。这就好比你在拖地,但是你的孩子却在跑来跑去,结果你一直拖不干净。

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

  • 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始阶段,所有的对象都是白色的,若在分析结束之后对象仍然为白色,则表示这些对象为不可达对象,对这些对象进行回收。

  • 灰色:表示对象已经被垃圾收集器访问过,但是这个对象至少存在一个引用(属性)还没有被扫描过。

  • 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经被扫描过。黑色表示这个对象扫描之后依然存活,是可达性对象,如果有其他对象引用指向了黑色对象,无须重新扫描,黑色对象不可能不经过灰色对象直接指向某个白色对象。

1.三色标记的基本过程

我们接下来看一下如何进行三色标记的:

初始状态

初始阶段只有GC Roots是黑色的,其他对象都是白色的,如果没有被黑色对象引用那么最终都会被当做垃圾对象回收。

 开始扫描

A和B均为扫描过的对象并且其引用也已经被垃圾回收器扫描过所以此时A、B对象均变为了黑色,而刚扫描到对象C,由于C的D和E还没有被扫描到,所以C暂时为灰色。

顺利扫描结束

此时扫描完成,黑色对象就是存活的对象,即可可达对象,白色对象G为不可达对象,在垃圾回收时就会被回收掉。

 2 三色标记的缺点

我们知道CMS和G1等垃圾回收器是一个并发回收的垃圾回收器,上面的图片只是展示了一种理想状态下的三色标记,实际上在并发的情况下会存在多标和漏标的问题。

 假如GC线程已经扫描到了E对象,此时E对象为灰色,这个时候用户线程将C的引用E断开,那么GC就会认为E对象是可达对象,而不会对E进行垃圾回收,但实际上E是个垃圾对象,这个时候就会产生多标问题,多标问题其实还可以接受,E作为浮动垃圾,那么等到下次垃圾回收的时候回收掉。

 

假如用户线程先断开了C到E的引用,那么E对象就认为是不可达对象,而此时B对象又引用了E对象,但是三色标记又不会重新从B点开始标记到E,那么E就会被认为是垃圾对象,但实际上E是有引用的,那么此时对E进行垃圾回收,之后就一定会产生错误,这就是漏标问题。

3 解决多标和漏标的问题

我们上面提到的多标带来的浮动垃圾问题。在并发标记过程中,如果由于方法运行结束导致部分局部变量(gcroot)被销毁,这个gcroot引用的对象之前又被扫描过(被标记为非垃圾对象),那么本轮GC不会回收这部分内存。这部分本应该回收但是没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除。

另外,针对并发标记(还有并发清理)开始后产生的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这部分对象期间可能也会变为垃圾,这也算是浮动垃圾的一部分。

但是漏标问题就比较严重了,漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案: 增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB) 。

上面演示了三色标记法存在的一些问题,那么如何解决这些问题呢。首先存在以上问题有两个条件同时满足才会发生

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

  • 赋值器删除了所有的从灰色对象到白色对象的直接引用或者间接引用。

那么要想解决并发扫描时对象消失的问题只需要破坏任何一个条件即可。因此产生了两种解决方案——增量更新(Incremental Update)和原始快照(Snapshot At The Beginning, SATB)。

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

原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后, 再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)。关于SATB的问题,我们下一篇单独分析。

增量更新破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时。就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象作为根对象,再重新扫描一遍。比如漏标问题中,一旦B对象直接指向了E对象,那么在并发扫描之后,就会将B对象作为灰色对象,再重新扫描一遍。这样虽然避免了漏标问题,但是重新标记会导致STW的时间变长。

原始快照破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后再将这些记录过的引用关系中的灰色对象为根对象再重新扫描一遍。例如漏标问题的途中,C断开E的引用关系时会保存一个快照,然后等扫描结束之后,会把C当作根再重新扫描一遍,假如B没有引用E,那么E对象也会认为是可达对象,这样E就成了浮动垃圾,只能等下次垃圾回收时再回收。

无论是对引用关系记录的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。在HotSpot虚拟机中,CMS是基于增量更新来做并发标记的,G1、Shenandoah则是用SATB来实现的。

现代追踪式(可达性分析)的垃圾回收器几乎都借鉴了三色标记的算法思想,尽管实现的方式不尽相同:比如白色/黑色集合一般都不会出现(但是有其他体现颜色的地方)、灰色集合可以通过栈/队列/缓存日志等方式进行实现、遍历方式可以是广度/深度遍历等等。

对于读写屏障,以Java HotSpot VM为例,其并发标记时对漏标的处理方案如下:

  • CMS:写屏障 + 增量更新

  • G1,Shenandoah:写屏障 + SATB

  • ZGC:读屏障

工程实现中,读写屏障还有其他功能,比如写屏障可以用于记录跨代/区引用的变化,读屏障可以用于支持移动对象的并发执行等。功能之外,还有性能的考虑,所以对于选择哪种,每款垃圾回收器都有自己的想法。

为什么G1用SATB?CMS用增量更新?

我的理解:SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC再深度扫描。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机视觉(CV)领域Transformer最新论文及资源整理分享

Transformer由论文《Attention is All You Need》提出,现在是谷歌云TPU推荐的参考模型。Transformer模型最早是用于机器翻译任务,当时达到了SOTA效果。Transformer改进了RNN最被人诟病的训练慢的缺点,利用self-attention机制实现快速并行。并…

梯度,GD梯度下降,SGD随机梯度下降

前言 羊了,但是依旧生龙活虎。补补之前落下的SGD算法,这个在深度学习中应用广泛。 梯度(Gradient) 方向导数 在梯度之前,非常重要一个概念:方向导数,这里uuu是nnn维向量,代表一个…

EMNLP 22:Bi-Directional Iterative Prompt-Tuning for Event Argument Extraction

总结 文中的前向和后向的思想可以借鉴下。 但总的来看,似乎是通过前向和后向来做的ensemble操作,虽然是在一个模型下,但同时前向和后向概率保证,可能能够使得预测更准确。 任务形式:event argument extraction (EAE)…

Java 读取resources下的文件+读取resource文件/路径

Java 读取resources下的文件 文档来源 三种实现方式 pom.xml <!-- commons-io io的工具包 --> <dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency>…

SSM之Spring(二)

目录 2.3 基于注解管理bean 2.3.1 标记与扫描 2.3.2 基于注解的自动装配 三&#xff1a;AOP 3.1 场景模拟 3.1.1 声明接口 3.1.2 创建实现类 3.1.3 创建带日志功能的实现类 3.1.4 提出问题 3.2 代理模式 3.2.1 概念 3.2.2 静态代理 3.2.3 动态代理 3.2.4 测试 3.3 AO…

ElasticSearch笔记

ELASTICSEARCH笔记 1、安装elastic search dokcer中安装elastic search &#xff08;1&#xff09;下载ealastic search和kibana docker pull elasticsearch:7.6.2 docker pull kibana:7.6.2&#xff08;2&#xff09;配置 mkdir -p /mydata/elasticsearch/config 创建目…

Hex程序烧写到单片机

一、创建一个Keil代码工程 1、在电脑F盘&#xff08;哪个盘可以随意选择&#xff09;上创建项目工程文件夹Template 2、在Template文件中&#xff0c;创建一个main.c文件 3、进入keil主页面&#xff0c;工具栏project---->New uVision project---->选则第一步的工程文…

【数据结构】时间与空间复杂度

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;《初识C语言》 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言一、算法效率1.1 如何衡量一个算…

【华为上机真题 2022】太阳能板的最大面积

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

ArcGIS基础实验操作100例--实验27细分栅格

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 基础编辑篇--实验27 细分栅格 目录 一、实验背景 二、实验数据 三、实验步骤 方法一&#xff1a;使用…

oneblog博客系统 让你的博客实现图片显示的功能

背景 \ 闲来无事&#xff0c;希望捣腾捣腾计算机&#xff0c;玩一玩网络&#xff0c;自己搭一个博客系统。那么在家搭建一个自己的服务器&#xff0c;并且让别人通过你的博客系统与你交流。多么神奇的事情。 在搭建博客系统的过程中&#xff0c;你需要在后台写好文章。但是在上…

网赚不要当苦力,流量变现才是王道

事实上&#xff0c;大多数互联网从业者脑子里只想到一件事。无脑搬砖一个月挣一万多元。不可否认&#xff0c;有这样的副业&#xff0c;但也需要前提条件。你需要在早期阶段努力工作&#xff0c;然后才能分批赚钱。这是两个概念&#xff0c;一开始就听话。 我从来不建议你做那…

leetcode 155. 最小栈-java题解

题目所属分类 辅助栈 或者一个栈 原题链接 设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。 void push(int val) 将元素val推入堆栈。 void pop() 删除堆栈顶部…

关于gets()与scanf()输入字符串与\0,\n,空格之间的问题

我们都知道输入字符串的话&#xff0c;用gets()函数与scanf()加%s的话都是可以的。同时&#xff0c;一个字符串的结尾默认有一个\0也是老生常谈。 空格 gets() 1. 当gets()碰到空格时就把它当成一个普通的字符来看&#xff0c;没有任何特殊之处可言。 scanf() 1. 如果再输入…

【自学Python】Python特点

Python特点 Python特点教程 Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性&#xff0c;相比其他语言经常使用英文关键字&#xff0c;其他语言的一些标点符号&#xff0c;它具有比其他语言更有特色语法结构。 Py…

计算机组成原理实验——一、ALU实验

一、实验目的 1.掌握ALU模块的组成和接口&#xff0c;理解ALU的功 能。 2.通过编程调用ALU模块计算斐波那契数。 3.掌握Verilog中多模块编程方法和实现。 二、实验内容 用 Verilog 设计一个算术运算单元 ALU&#xff0c;采 用纯组合逻辑设计&#xff0c;32bit 宽。 利用该 …

2022(终)最后一篇博客—继承和多态

目录 文章目录 一、继承 1.1为什么要继承 1.2继承概念 1.3继承的语法 1.4成员访问 1.5方法访问 1.6初始化代码块 1.7继承方式 二、多态 1.1多态的概念 1.2多态实现条件 1.3动态绑定与静态绑定 1.4向上转型与向下转型 1.5多态的优缺点 1.6需要注意的地方&#x…

【王道操作系统】1.1.2 操作系统的特征(并发、共享、虚拟、异步)

操作系统的特征(并发、共享、虚拟、异步) 文章目录操作系统的特征(并发、共享、虚拟、异步)1.操作系统特征&#xff1a;并发2.操作系统特征&#xff1a;共享3.操作系统特征&#xff1a;虚拟4.操作系统特征&#xff1a;异步操作系统是一种系统软件&#xff0c;但与其它系统软件和…

NXP的mfgtool镜像烧写工具是如何对EMMC进行分区的

本来是想在IMX6ULL板卡上实现u-boot中显示开机logo的功能的&#xff0c;过程中就牵扯出了这几个问题。大概的缘由是修改了u-boot后&#xff0c;想在系统中直接对EMMC中u-boot进行更新&#xff0c;就不用通过SD卡或USB重新烧写了&#xff1b;在更新的时候&#xff0c;又需要知道…

猿如意程序代码生成实践与测试

ChatGPT中国区申请无法直接注册与使用&#xff1b; 使用csdn提供的猿如意效率工具箱提供的chatGPU功能实现智能程序编写实验&#xff1a; 先安装&#xff1a; csdn猿如意下载地址 选择其中的ChatGPT菜单 在右侧窗口中的输入框&#xff0c;可输入问题内容。 &#xff08;1&a…