垃圾回收之三色标记法(Tri-color Marking)

news2024/10/5 15:22:44

关于垃圾回收算法,基本就是那么几种:标记-清除、标记-复制、标记-整理。在此基础上可以增加分代(新生代/老年代),每代采取不同的回收算法,以提高整体的分配和回收效率。

无论使用哪种算法,标记总是必要的一步。你不先找到垃圾,怎么进行回收?今天一起看下三色标记法。

先看一下知识点导图:

一、如何标记

在 GC 领域里,判断对象存活的主流思路是两个,「引用计数」和「可达性分析」。

1、引用计数

顾名思义,引用计数的思路就是给每个对象进行计数,每被其它对象引用一次,计数就 +1,引用失效后,计数就 -1。当计数器的数值为 0,就意味着它没有被使用,可以回收。

2、可达性分析

可达性分析的思路就是通过引用链路判断对象是否可被触达,如果能触达说明该对象当前正在被使用,不可回收;反之,没有触达到的对象则认为是无使用的,可以回收。

这个引用链路的结构类似于有向有环图,但是根节点不止一个,是一个集合,称之为 GCRoots。

目前主流的 GC 机制大多用的是「可达性分析」这条路线。

为什么引用计数不好用呢?因为它有一个特别严重的问题:无法处理循环引用。

像上图这样的情况,引用计数永远不为 0,这些对象就永远不会被回收。

二、常规标记-清除

常规的标记清除严格按照追踪式算法的思路来实现的。这个算法会设置一个标志位来记录对象是否被使用。最开始所有的标记位都是 0,如果发现对象是可达的就会置为 1,一步步下去就会呈现一个类似树状的结果。

等标记的步骤完成后,会将未被标记的对象统一清理,再次把所有的标记位设置成 0 方便下次清理。

标记清除法主要包含两个步骤:

  • 标记
  • 清除

示例如下:

1、开启STW,停止程序的运行,图中是本次GC涉及到的root节点和相关对象。

 

2、从根节点出发,标记所有可达对象。

3、停止STW,然后回收所有未被标记的对象

这样执行整个GC期间需要STW,将整个程序暂停。因为如果不进行STW的话,会出现已经被标记的对象A,引用了新的未被标记的对象B,但由于对象A已经标记过了,不会再重新扫描A对B的可达性,从而将B对象当做垃圾回收掉的问题。

三、三色标记

垃圾收集器依据可达性分析算法判断对象是否存活时,将遍历GC Roots过程中遇到的对象,按照“是否访问过”这个条件,把对象标记成白色(white)、灰色(gray)、黑色(black)三种颜色,这个标记过程称为三色标记法。

相比传统的标记清扫算法,三色标记最大的好处是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个 GC。

1、基本算法

三色标记法将对象用三种颜色表示,分别是白色、灰色和黑色。

最开始所有对象都是白色的,然后把其中全局变量和函数栈里的对象置为灰色。

第二步把灰色的对象全部置为黑色,然后把原先灰色对象指向的变量都置为灰色,以此类推。

等发现没有对象可以被置为灰色时,所有的白色变量就一定是需要被清理的垃圾了。

  • 初始标记阶段,指的是标记 GCRoots 直接引用的节点,将它们标记为灰色,这个阶段需要 「Stop the World」。
  • 并发标记阶段,指的是从灰色节点开始,去扫描整个引用链,然后将它们标记为黑色,这个阶段不需要「Stop the World」。
  • 重新标记阶段,指的是去校正并发标记阶段的错误,这个阶段需要「Stop the World」。
  • 并发清除,指的是将已经确定为垃圾的对象清除掉,这个阶段不需要「Stop the World」。

三色标记法是一个 false negative(假阴性)的算法:

  • 三色标记法因为多了一个白色的状态来存放不确定的对象,所以可以异步地执行。
  • 当然异步执行的代价是可能会造成一些遗漏,因为那些早先被标记为黑色的对象可能目前已经是不可达的了。

2、现代垃圾回收器实现

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

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

  • CMS:写屏障 + 增量更新
  • G1:写屏障 + SATB
  • ZGC:读屏障

四、多标及漏标问题

三色标记算法缺陷:在并发标记阶段的时候,因为用户线程与GC线程同时运行,有可能会产生多标或者漏标;

  • 多标--多标记(浮动垃圾)
  • 漏标--漏标记

1、多标问题

并发标记:用户与GC线程同时运行,假设现在扫描到C对象,B对象变为黑色,用户线程执行C的属性E=null,GC线程扫描C对象引用链,认为E对象是为可达对象,但是C对象根本没有引入到E对象,E对象应该是为垃圾对象,这种问题,可以在重新标记阶段(修正)修复。

并发清除阶段:用户与GC线程同时运行,会产生新的对象但是没有及时被GC清理。

多标只能在下一次GC清理垃圾的修复。

2、漏标问题

1.用户线程先执行C的E属性=null;GC线程的GcRoot就扫描不到E。Gc就认为E对象就是为垃圾对象,不可达对象。

2.用户线有执行B.E属性=E;E对象就是应该是为可达对象。

3.因为GCRoot是从C开始,不会从黑色的B开始,就会导致漏标的情况发生。

漏标的问题满足两个条件:

  1. 有至少一个黑色对象在自己被标记之后指向了这个白色对象
  2. 所有的灰色对象在自己引用扫描完成之前删除了对白色对象的引用

 只有当上面两个条件都满足,三色标记算法才会发生漏标的问题。换言之,如果我们破坏任何一个条件,这个白色对象就不会被漏标。

CMS如何解决漏标问题---写屏障+增量更新方式

满足一个条件(灰色对象与白色对象断开连接),在并发标记阶段当我们黑色对象(B)引用关联白色对象(E),记录下B黑色对象。

在重新标记阶段(所有用户线程暂停),有将B对象变为灰色对象将整个引用链全部扫描。

缺点:遍历B整个链的效率非常低,有可能会导致用户线程等待的时间非常长。

G1如何解决漏标问题---原始快照方式

在C断开E的时候,会记录原始快照,在重新标记阶段的时候以白色对象变为灰色为起始点扫描整个链,本次GC是不会被清理。

好处:如果假设B(黑色对象)引入该白色对象的时候,无需做任何遍历效率是非常高。

缺点:如果假设B(黑色对象) 没有引入该白色对象的时候,该白色对象在本次GC继续存活,只能放在下一次GC在做并发标记的时候清理。

tips:以浮动垃圾(占内存空间)换让我们用户线程能够暂停的时间更加短。

总结:

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

  • CMS:采用的是写屏障 + 增量更新
  • G1: 采用的是写屏障 + 原汁快照(SATB)
  • ZGC:采用的是读屏障

CMS收集器解决漏标问题:增量方式 如果现在B(黑色)对象引入白色对象,写屏障。

好处:避免浮动垃圾,缺点扫描整个引用链效率比较低。

G1收集器解决漏标问题:原始快照方式。

好处:效率非常高,无需扫描整个引用链,缺点:可能会产生浮动垃圾。 

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

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

相关文章

(已解决)RuntimeError: Java gateway process exited before sending its port number

今天用Pycharm远程使用pysaprk解释器时,跑代码出现了这个错误: RuntimeError: Java gateway process exited before sending its port number 找了好多博客都没解决问题,有说重装spark的,有说本地配Java_home的,后面我…

[C语言刷题]杨氏矩阵、返回型参数

本文包含知识点 杨氏矩阵极其解法函数return多个值的四种方法 题目: 杨氏矩阵 有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。 要求:时间复杂度小于…

js 在浏览器窗口关闭后还可以不中断网络请求

有个需求,我们需要在用户发送数据过程中,如果用户关闭了网页(包括整个浏览器关闭),不要中断数据传递 目前XMLHttpRequest对象是不支持的 http服务器 为了测试效果我们用nodejs写了个http服务器代码 文件名为httpServer.js如下,…

获取大疆无人机的飞控记录数据并绘制曲线

机型M350RTK,其飞行记录文件为加密的,我的完善代码如下 gitgithub.com:huashu996/DJFlightRecordParsing2TXT.git 一、下载安装官方的DJIFlightRecord git clone gitgithub.com:dji-sdk/FlightRecordParsingLib.git飞行记录文件在打开【我的电脑】&am…

Windows nvm 安装后webstrom vue项目编译报错,无法识别node

1 nvm安装流程 卸载原先nodejs用管理员权限打开exe安装nvmnvm文件夹和nodejs文件夹 都授权Authenticated Users 完全控制nvm list availablenvm install 16.20.1nvm use 16.20.1输入node和npm检查版本命令,正常显示确认系统变量和用户变量都有nvm 和nodejs 2 bug情…

数学建模-聚类算法 系统(层次)聚类

绝对值距离:网状道路 一般用组间和组内距离 聚类的距离计算如何选取:看结果是否解释的通,选择一种结果解释的通的方法。

【数据挖掘】将NLP技术引入到股市分析

一、说明 在交易中实施的机器学习模型通常根据历史股票价格和其他定量数据进行训练,以预测未来的股票价格。但是,自然语言处理(NLP)使我们能够分析财务文档,例如10-k表格,以预测股票走势。 二、对自然语言处…

【转载+修改】pytorch中backward求梯度方法的具体解析

原则上,pytorch不支持张量对张量的求导,它只支持标量对张量的求导 我们先看标量对张量求导的情况 import torch xtorch.ones(2,2,requires_gradTrue) print(x) print(x.grad_fn)输出,由于x是被直接创建的,也就是说它是一个叶子节…

Vue.js uni-app 混合模式原生App webview与H5的交互

在现代移动应用开发中,原生App与H5页面之间的交互已经成为一个常见的需求。本文将介绍如何在Vue.js框架中实现原生App与H5页面之间的数据传递和方法调用。我们将通过一个简单的示例来展示如何实现这一功能。附完整源码下载地址:https://ext.dcloud.net.cn/plugin?i…

Java集成openAi的ChatGPT实战

效果图: 免费体验地址:AI智能助手 具体实现 public class OpenAiUtils {private static final Log LOG LogFactory.getLog(OpenAiUtils.class);private static OpenAiProxyService openAiProxyService;public OpenAiUtils(OpenAiProxyService openAiP…

【C++】入门 --- 命名空间

文章目录 🍪一、前言🍩1、C简介🍩2、C关键字 🍪二、命名冲突🍪三、命名空间🍩1、命名空间定义🍩2、命名空间的使用 🍪四、C输入&输出 🍪一、前言 本篇文章是《C 初阶…

Data Transfer Object-DTO,数据传输对象,前端参数设计多个数据表对象

涉及两张表的两个实体对象 用于在业务逻辑层和持久层(数据库访问层)之间传输数据。 DTO的主要目的是将多个实体(Entity)的部分属性或多个实体关联属性封装成一个对象,以便在业务层进行数据传输和处理,从而…

八、HAL_UART(串口)的接收和发送

1、开发环境 (1)Keil MDK: V5.38.0.0 (2)STM32CubeMX: V6.8.1 (3)MCU: STM32F407ZGT6 2、UART和USART的区别 2.1、UART (1)通用异步收发收发器:Universal Asynchronous Receiver/Transmitter)。 2.2、USART (1)通用同步异步收发器:Universal Syn…

【《R4编程入门与数据科学实战》——一本“能在日常生活中使用统计学”的书】

《R 4编程入门与数据科学实战》的两名作者均为从事编程以及教育方面的专家,他们用详尽的语言,以初学者的角度进行知识点的讲解,每个细节都手把手教学,以让读者悉数掌握所有知识点,在每章的结尾都安排理论与实操相结合的习题。与同…

banner轮播图实现、激活状态显示和分类列表渲染、解决路由缓存问题、使用逻辑函数拆分业务(一级分类)【Vue3】

一级分类 - banner轮播图实现 分类轮播图实现 分类轮播图和首页轮播图的区别只有一个,接口参数不同,其余逻辑完成一致 适配接口 export function getBannerAPI (params {}) {// 默认为1 商品为2const { distributionSite 1 } paramsreturn httpIn…

VTK是如何显示一个三维立体图像的

VTK是如何显示一个三维立体图像的 1、文字描述2、图像演示 1、文字描述 2、图像演示

MySQL-事务-介绍与操作

思考 假设在一个场景中,学工部解散了,需要删除该部门及该部门下的员工对应的SQL语句涉及的数据表信息如下 员工表 部门表 实现的SQL语句 -- todo 事务 -- 删除学工部 -- 删除1号部门 delete from tb_dept where id 1; -- 删除学工部下的员工 delete …

SPEC CPU 2006 docker gcc:4 静态编译版本 Ubuntu 22.04 LTS 测试报错Invalid Run

runspec.sh #!/bin/bash source shrc ulimit -s unlimited runspec -c gcc41.cfg -T all -n 1 int fp > runspec.log 2>&1 & tail -f runspec.log runspec.log 由于指定了-T all,导致-n 1 失效,用例运行了三次(后续验证&…

【LeetCode 75】 第十题(283)移动零

目录 题目: 示例: 分析: 代码运行结果: 题目: 示例: 分析: 给一个数组,要求将数组中的零都移动到数组的末尾. 首先我们可以遍历一边数组,遇到0的时候就在数组中把0删除,并且统计0的数量. 遍历完成以后数组中就没有0了,这时我们再在数组的后面添上之前统计的0的数量个0. …

IntelliJ IDEA Copyright添加

IDEA代码文件的版权(copyright)信息配置 1. 快速创建Copyright 版权配置文件 1.1 创建copyright文件 依次点击 File > Settings… > Editor > Copyright > 点击 “” 号或 “Add profile”***,弹出创建 Copyright Profile 操作窗口,在***文…