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

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

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

先看一下知识点导图:

一、如何标记

在 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收集器解决漏标问题:原始快照方式。

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

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

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

相关文章

Gitlab服务器备份恢复及系统升级

居安思危,思则有备,有备无患。 基于此,申请了一个测试服务器,准备先安装同版本服务器,按照最新的数据进行恢复,然后再将现在的服务器升级到Gitlab的最新版本,记录一下完整的过程,以…

【第一阶段】varval类型推断

Val 可读不可改 代码示例: 不可改: fun main() {//val可读不可改val name:String"kotlin"//不可改 此时会报错 报错打印信息:Val cannot be reassignedname"java" }可读: fun main() {//val可读不可改val…

uniapp WIFI上下班打卡

大纲 🥙 uniapp官网:uni-app官网 🥙 WIFI功能模块: 1、下载 wifi 插件 uni-WiFi 2、在 manifest.json 中 App权限配置中 配置权限 1. ACCESS_WIFI_STATE (访问权限状态) 2. CHANGE_WIFI_STATE&#xff…

Qt 第三讲 手动拖动ui界面组件,事件处理机制,实现简单闹钟

手动拖动ui界面组件,实现闹钟 源文件 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->btn_end->setEnabled(false);speecher new QTex…

flask结合mysql实现用户的添加和获取

1、数据库准备 已经安装好数据库,并且创建数据库和表 create database unicom DEFAULT CHARSET utf8 COLLATE utf8_general_ci; CREATE TABLE admin( id int not null auto_increment primary key, username VARCHAR(16) not null, password VARCHAR(64) not null…

【QT】Day1

1. 收到实现登录框 要求&#xff1a; 1、登录窗口更改标题、图标 2、设置固定尺寸、并给定一定的透明度 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QDebug> //信息调试类&#xff0c;用于打印输出的 #include <QIcon>…

【雕爷学编程】MicroPython动手做(02)——尝试搭建K210开发板的IDE环境2

知识点&#xff1a;Micropython的来历 MicroPython是英国剑桥大学的教授 Damien George&#xff08;达米安乔治&#xff09;所发明&#xff0c;Damien George 是一名计算机工程师&#xff0c;他每天都要使用 Python 语言工作&#xff0c;同时也在做一些机器人项目。有一天&…

出海周报|Temu在美状告shein、ChatGPT安卓版上线、小红书回应闪退

工程机械产业“出海”成绩喜人&#xff0c;山东相关企业全国最多Temu在美状告shein&#xff0c;跨境电商战事升级TikTok将在美国推出电子商务计划&#xff0c;售卖中国商品高德即将上线国际图服务&#xff0c;初期即可覆盖全球超200个国家和地区ChatGPT安卓版正式上线&#xff…

字典序排数(力扣)思维 JAVA

给你一个整数 n &#xff0c;按字典序返回范围 [1, n] 内所有整数。 你必须设计一个时间复杂度为 O(n) 且使用 O(1) 额外空间的算法。 示例 1&#xff1a; 输入&#xff1a;n 13 输出&#xff1a;[1,10,11,12,13,2,3,4,5,6,7,8,9] 示例 2&#xff1a; 输入&#xff1a;n 2 输…

【LeetCode】101.对称二叉树

题目 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false提示&#xff1a; 树中节点数…

pg三种插件验证

sr_plan 创建extension, 他会创建保留执行计划的表 创建表并插入数据 开启sr_plan.write_mode, 允许sr_plan收集SQL和执行计划 查看QUERY 1的执行计划 PostgreSQL支持merge join、GroupAggregate(通过INDEX SCAN)&#xff0c;所以这个CASE&#xff0c;非常快&#xff0c;并…

Dubbo 指定调用固定ip+port dubbo调用指定服务 dubbo调用不随机 dubbo自定义调用服务 dubbo点对点通信 dubbo指定ip

1. 在写分布式im时nami-im: 分布式im, 集群 zookeeper netty kafka nacos rpc主要为gate&#xff08;长连接服务&#xff09; logic &#xff08;业务&#xff09; lsb &#xff08;负载均衡&#xff09;store&#xff08;存储&#xff09; - Gitee.com&#xff0c;需要指定某一…

FANUC机器人SRVO-050碰撞检测报警和SRVO-053干扰值过大故障报警总结

FANUC机器人SRVO-050碰撞检测报警和SRVO-053干扰值过大故障报警总结 前面和大家分享了关于SRVO-050碰撞检测报警和SRVO-053干扰值过大的原因分析以及处理方法,感兴趣的朋友可以参考以下链接中的内容: FANUC机器人SRVO-050碰撞检测报警原因分析及处理对策

图书馆荐书《乡村振兴战略下传统村落文化旅游设计》

图书馆荐书《乡村振兴战略下传统村落文化旅游设计》 书名&#xff1a;乡村振兴战略下传统村落文化旅游设计 索取号&#xff1a;F592.3/47 作者&#xff1a;许少辉 简介&#xff1a;我国传统村落具有宝贵的历史价值、农业价值和生态价值等价值特色&#xff0c;在生动开展基于…

Android 通用带箭头提示窗

简介 自定义PopupWindow, 适用于提示类弹窗。 使用自定义Drawable设置带箭头的背景&#xff0c;测试控件和弹窗的尺寸&#xff0c;自动设置弹窗的显示位置&#xff0c;让箭头指向锚点控件的中间位置&#xff0c;且根据锚点控件在屏幕的位置&#xff0c;自动适配弹窗显示位置。…

vue项目开发环境和生产环境代理的配置问题

1.跨域 跨域解决方案&#xff1a; 1.JSONP 通过动态 script标签跨域 2.document.domain iframe跨域 3.location.hash iframe 4.window.name iframe跨域 5.postMessage 跨 window 通信 6.跨域资源共享&#xff08;CORS&#xff09; 7.nginx代理跨域 8.nodejs中间件代理跨域 9…

283. 移动零

题目 题解一&#xff1a; 反向收集不是0 的数字放到一个数组里面&#xff0c;用原数组大小减去收集数组的大小就是0 的个数 /*** 反向收集不是0 的数字放到一个数组里面&#xff0c;用原数组大小减去收集数组的大小就是0 的个数* param nums*/public static void moveZeroes(i…

8. Spring Boot 日志文件

目录 1. 日志的作用 2. 如何使用日志 3. 自定义日志打印 3.1 获取日志对象 3.2 设置打印的内容 3.3 常见的日志框架 3.4 日志格式说明 4. 日志级别 4.1 日志级别的作用 4.2 日志级别的分类 4.3 日志级别的使用 4.4 设置日志级别 4.5 分目录设置日志级别 5. 日志…

《Docker与持续集成/持续部署:构建高效交付流程,打造敏捷软件交付链》

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

Qt5.14.2+VS2019配置MSVC2017

问题&#xff1a; The compiler " Microsoft Visual C Compiler 16 . 11 . 32106 . 194 ( amd64 x86 )( x86-windows-msvc2019-pe-32bit ) cannot produce code for the Qt version " Qt5.14.2 MSVC2017 64bit " ( x86-windows-msvc2017-pe-64bit 编译器“…