JVM 三色标记算法

三色标记算法核心原理

三色标记算法是一种JVM的垃圾标记算法,CMS/G1垃圾回收器就是使用的这种算法,它可以让JVM在不发生或者尽可能短的发生STW(Stop The World)的情况下进行垃圾的标记和清除。

顾名思义,三色标记算法是将Java堆中的对象分为了三种颜色,分别是:

  1. 白色:白色对象代表没有被标记过的对象,在GC标记阶段刚开始的时候所有对象都是白色对象;而在GC标记阶段结束的时候,所有白色对象表示没有被引用的对象(即垃圾)。
  2. 灰色:灰色对象表示该对象已经被标记过了,但是其引用的对象还没有被完全标记,JVM需要遍历其子对象来找到可达对象和垃圾。
  3. 黑色:黑色对象表示该对象及其子对象全部都被标记过了,说明该对象已经完成了标记,JVM无需继续处理它。

在这里插入图片描述
如上图所示,以CMS垃圾回收流程为例展示三色标记算法中每种颜色对应的意义:

  1. 初始状态:初始状态时,GC标记还没开始,此时所有的Java对象都是白色对象,即未标记对象;
  2. 初始标记:在初始标记阶段为了缩短STW时间,只标记了GC Roots直接可达的对象。那么这个过程中标记的GC Roots直接可达的对象就是灰色对象,因为这些对象可能包含子对象还并未被完全标记。
  3. 并发标记:在并发标记阶段GC线程会从灰色对象出发,依次标记其所含的子对象,知道所有子对象都被标记完了之后,这些对象就称为了黑色对象。
    3.1 阶段1:此时GC Roots直达对象A、B、C被标记称黑色,而它们的子对象D、F、G被标记成灰色;
    3.2 阶段2:接着D、F、G对象被标记为黑色,而子对象H被标记成灰色;
    3.3 阶段3:H也被标记成黑色,此时所有对象都被标记成了黑色,对象标记完成;
  4. 垃圾清理:在前面的标记过程中,由于所有的灰色对象都有GC Roots可以到达,所以这些黑色对象都是存活对象,而白色对象则是垃圾,需要被清理。

三色标记算法的优势在于是一种增量型的垃圾标记算法,一步步的标记出整个堆中的所有垃圾而无需长时间的STW,造成系统卡顿。

三色标记算法存在的问题

由于在并发标记阶段,GC线程和用户线程是并发运行的,随时可能发生对象之间引用变化从而导致“多标”和“漏标”的问题。

多标问题
在这里插入图片描述

如上图所示,在并发标记阶段,当对象G已经被标记成灰色对象之后,此时C到G得引用被干掉了。

那么此时,C的子对象G和H应该被标记为白色,因为从GC Roots到他们的引用已经消失了,它们已经成为了垃圾对象。

但是由于G已经被标记成为了灰色对象,所以GC线程并不知道G和H需要被标记为白色,而是继续将这两个对象标记成了黑色对象 。

最终结果导致,G和H这部分对象依旧存活,不会被本轮GC所清理,也就产生了浮动垃圾。

漏标问题
在这里插入图片描述
如上图所示,在并发标记阶段,当G已经被标记成了灰色对象,F已经被标记成为了黑色对象,而此时G->H的引用被断开,但是又新建立了一个F->H的引用。

那么此时,由于F已经被标记成为了黑色,那么F的子对象H就不会被扫描和标记到,因此就产生了“漏标”。这样就会导致H对象一直是白色,最后被当作垃圾被清除,这样直接会影响到程序运行的正确性,因此是绝对要被避免的。

实际上,漏标记只有同时满足以下两个条件才能发生:

  1. 一个或者多个黑色对象重新引用了白色对象,即黑色对象成员变量增加了新的引用;
  2. 灰色对象断开了白色对象的引用(直接或者间接),即灰色对象原来的成员变量引用发生了变化;

在上面的三个步骤当中,我们只需要在任意一步的时候讲被漏标的对象H存储起来,然后作为一个灰色对象放入到一个集合当中。等到并发标记结束重新标记的时候,遍历这个集合再次标记剩余对象就可以解决漏标的问题。

但是,这样一来“重新标记”就需要STW,因为只要有用户线程在执行就有可能会出现新的漏标记的情况发生,导致永远无法完成GC的标记过程。不过好在前面的“并发标记”过程已经将大部分的垃圾标记出来了,所以“重新标记”过程所耗费的时间较低。

所以说三色标记算法也无法完全解决STW问题,只能尽可能缩短STW的时间,从而减少系统的停顿。

如何解决漏标问题?

对于多标问题来说,由于像CMS这类垃圾回收算法本身就会产生浮动垃圾,所以不算特别严重的问题,只需要等到下一次GC再回收即可。

但是漏标问题就相对严重得多,它会错误的回收正常的对象,导致程序错误,因此必须避免漏标的情况发生,下面主要介绍漏标的解决方案。

内存屏障方案

针对漏标问题,JVM 团队采用了读屏障与写屏障的方案。读屏障拦截了第一步,而写屏障用于拦截第二和第三步。它们的目的是在读写操作前后记录漏标对象H。下面是读屏障和写屏障的相关实现:

在这里插入图片描述
这里的读屏障直接针对第一步 ObjectH H = objG.fieldH; ,在读取成员变量之前先记录下来。这种做法是保守但安全的,因为重新引用的前提是获取到该白色对象,此时读屏障发挥了作用。

但是读屏障并不是所有对象的读写操作都会执行,只有在“并发标记”阶段($gc_phase == GC_CONCURRENT_MARK)并且对象未被标记(白色对象,!isMarked(field))的情况下才执行。这保证了读屏障的触发是有条件的,并不会对所有对象的读操作都进行记录。

写屏障——写入之前记录漏标对象H

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); // 写屏障-写后操作
}

这样的设计在写操作前后加入了相应处理,保证了在写操作时对相关对象的状态进行记录和处理。

写屏障同样是在并发标记阶段执行的。在写操作前后加入了处理,包括写前操作 pre_write_barrier 和写后操作 post_write_barrier。写屏障的触发同样是有条件的,不是所有对象的写操作都会记录。

增量更新(Incremental Update)与原始快照(Snapshot At The Beginning,SATB)方案

增量更新(Incremental Update)
在对象D的成员变量引用发生变化时(例如 objF.fieldH = H;),通过写屏障,将F新的成员变量引用对象H记录下来:

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

这种做法的思路是不要求保留原始快照,而是针对新增的引用将其记录下来等待遍历,即增量更新。增量更新破坏了漏标的条件一:“一个或多个黑色对象重新引用了白色对象”,从而保证了不会漏标。

原始快照(Snapshot At The Beginning,SATB)
当对象E的成员变量引用发生变化时(例如 objG.fieldH = null;),通过写屏障,将E原来成员变量的引用对象G记录下来:

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

在原来成员变量引用发生变化之前,记录下原来的引用对象。这种做法的思路是尝试保留开始时的对象图,即原始快照(Snapshot At The Beginning,SATB)。当某个时刻的GC Roots确定后,当时的对象图就已经确定了。如果期间发生变化,则可以记录起来,保证标记依然按照原本的视图来。SATB破坏了漏标的条件二:“灰色对象断开了白色对象的引用(直接或间接的引用)”,从而保证了不会漏标。

主流垃圾回收器的漏标处理方案

基于可达性分析的GC算法,尤其是在并发标记的情境下,各种垃圾收集器采用了不同的漏标处理方案。在Java HotSpot VM中,具体的处理方式如下:

  1. CMS(Concurrent Mark-Sweep)——****写屏障 + 增量更新: 在并发标记阶段,CMS采用写屏障机制,通过在对象引用发生变化时进行增量更新,将新的引用记录下来。这有助于防止漏标。增量更新相较于其他方案可能会引入更多的浮动垃圾,但在重新标记阶段无需深度扫描已被删除引用的对象。

  2. G1(Garbage-First)——****写屏障 + 原始快照: G1在并发标记时采用写屏障,并选择了原始快照的方式。这意味着在对象引用发生变化时,将原始引用记录下来。相较于增量更新,原始快照在重新标记阶段可能会更高效,因为不需要深度扫描已被删除引用的对象。这对于G1来说是一个合适的选择,因为它的对象分布在不同的区域,而不像CMS一样集中在一个老年代区域。

  3. Shenandoah——****写屏障 + 原始快照:类似于G1,Shenandoah在并发标记时采用写屏障,并选择了原始快照的方式。这种设计可以提高效率,因为在重新标记阶段不需要深度扫描已被删除引用的对象。

  4. ZGC(Z Garbage Collector)——****读屏障 + 染色指针:ZGC采用了染色指针技术,通过读屏障来处理并发标记。这种设计可以显著减少内存屏障的使用数量,特别是写屏障。染色指针直接在指针中维护引用变动的信息,避免了一些记录操作,对性能有显著的帮助。

这些选择基于不同垃圾收集器的特点和设计目标,以在并发标记过程中尽可能降低对应用程序的影响,并提高垃圾回收的效率

总结

三色标记算法是一种用于JVM垃圾回收的增量式标记算法,它将Java堆中的对象划分为白色、灰色和黑色三种颜色,分别表示未标记对象、部分标记对象和已完全标记对象。这算法被广泛应用于CMS(Concurrent Mark-Sweep)和G1(Garbage-First)等垃圾回收器中,以在GC标记和清理阶段尽量减少Stop-The-World(STW)的时间,提高系统的响应性。

总体而言,三色标记算法通过增量标记的方式,尽量避免全局性的STW,提高了垃圾回收的并发性,但也需要处理漏标问题。各种垃圾回收器在实际应用中选择不同的方案来平衡性能和准确性的要求。

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

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

相关文章

【名词解释】Unity中的3D物理系统:刚体

Unity中的3D物理系统是用于模拟现实世界中物体的运动和相互作用的一套工具和组件。刚体(Rigidbody)是Unity 3D物理系统中的一个核心组件,它允许游戏对象(GameObject)受到重力和外力的影响,并参与碰撞检测。…

实现JWT认证与授权的Spring Boot项目详解

我们将详细介绍如何使用JWT(JSON Web Tokens)结合Spring Boot框架实现用户认证和授权系统。此方案将包括用户注册、登录以及通过JWT令牌进行后续请求的身份验证过程。我们将从引入必要的依赖开始,然后逐步构建项目的各个部分,包括…

精品丨PowerBI迁移到SSAS

业务场景: 企业初期在进行 BI 可视化路线的时候,往往不会选择方案较为完整的SSAS,而是会选择轻量的 PowerBI 方案,究其根本还是软件成本的问题。 但是随着模型越来越臃肿,维护成本越来越高,有很多模型需要进…

【名词解释】Unity中的3D坐标系

Unity中的3D坐标系是一个基于右手定则的笛卡尔坐标系,它定义了Unity场景中所有3D对象的位置、旋转和缩放。以下是一些基本的名词解释和使用方法: 名词解释: X轴:水平方向,从屏幕左侧向右延伸。Y轴:垂直方…

SQL Auto Increment

SQL Auto Increment 在关系型数据库中,自动增量(Auto Increment)是一个常见且实用的特性。它允许数据库自动为表中插入的新行分配唯一的标识符,通常用于主键字段。本文将深入探讨SQL中的自动增量功能,包括其工作原理、…

Java面向对象-抽象类和抽象方法

Java面向对象-抽象类和抽象方法 1、代码案例展示2、抽象类和抽象方法的关系: 1、代码案例展示 1、在一个类中会有一类方法,无需重写,直接使用 2、在一个类中会有一类方法,会对这个方法进行重写 3、一个方法的方法体去掉&#xff…

【文心智能体分享】日记周报助手

引言 在繁忙的实习生活中,你是否曾为如何整理日常的工作日志、周报、月报而烦恼?现在,我们为你带来了一个全新的智能体——“日记周报助手”,它将成为你实习过程中的得力助手,帮你轻松整理实习日志,让你的…

mysql 中的锁

一.锁的介绍 锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,除了传统的计算资源(cpu,ram,i/o)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性…

【C#】IndexOf的使用

1. 在ObservableCollection<T> 类中使用&#xff1a; ObservableCollection<T> 类是 C# 中的一个集合类&#xff0c;它继承自 Collection<T> 并实现了 INotifyCollectionChanged 接口&#xff0c;使得集合的更改可以自动通知给任何监听它的界面元素。这使得…

初见 Rollup 的十大常见问题

文章目录 初见 Rollup 的十大常见问题1. 超神奇的 Rollup 英文解释&#xff01;2. 为什么 ESM 要比 CommonJS 要好呢&#xff1f;3. 什么是 tree-shaking ?4. 如何使用 Rollup 处理 CommonJS&#xff1f;5. 为什么 node-resolve 不是一个内置功能&#xff1f;6. 为什么在进行代…

如何警用root用户登录ssh

使用tail指令&#xff0c;可以动态查看日志信息。 &#xff08;tail -f /var/log/secure或messages&#xff09; 使用>符号&#xff0c;可以清空日志内容&#xff0c;不删除文件本身。 禁用root用户为以下步骤&#xff1a; 首先使用useradd创建用户&#xff08;可以修改为其…

STM32HAL-最简单的时间片论法

目录 概述 一、开发环境 二、STM32CubeMx配置 三、编码 四、运行结果 五、总结 概述 本文章使用最简单的写法时间片论法框架,非常适合移植各类型单片机,特别是资源少的芯片上。接下来将在stm32单片机上实现,只需占用1个定时器作为tick即可。(按键框架+时间片论法)…

20240613每日前端--------聊聊根据面试简历面试的一位高级前端开发工程师

项目经验 封装了通用的表单组件&#xff0c;支持多表单结构&#xff0c;如&#xff1a;富文本编辑器、文件上传等封装了 Echarts 图表组件&#xff0c;可以展示各种报表数据封装了通用的表格组件&#xff0c;支持多条件搜索及分页功能封装了 svg 图标上传组件&#xff0c;将下…

【数据结构之B树的讲解】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

【乐吾乐2D可视化组态编辑器】开关、阀门、报警状态切换

开关状态 开关的断开与闭合&#xff1a;将电力组件的“开”与“关”2个组件重叠在一起&#xff0c;右键选择“组合为状态”&#xff0c;属性面板中就可以任意切换状态。 视频教程&#xff1a;开关阀门多状态控制 乐吾乐2D可视化组态编辑器地址&#xff1a;https://2d.le5le.co…

【python】python指南(三):使用正则表达式re提取文本中的http链接

一、引言 对于算法工程师来说&#xff0c;语言从来都不是关键&#xff0c;关键是快速学习以及解决问题的能力。大学的时候参加ACM/ICPC一直使用的是C语言&#xff0c;实习的时候做一个算法策略后台用的是php&#xff0c;毕业后做策略算法开发&#xff0c;因为要用spark&#x…

开发指南030-常用的工具网站

1、下载jdk https://mirrors.huaweicloud.com/java/jdk/ 2、在线解析二维码 http://cdn.malu.me/qrdecode/ 3、Properties和Yaml格式互转 https://www.toyaml.com/index.html 4、生成banner https://devops.datenkollektiv.de/banner.txt/index.html 5、二维码生成器 http…

js编程环境配置-vscode

1、安装Node.js 官网下载 选择适合你Windows系统架构&#xff08;32位或64位&#xff09;的安装包。windows系统选择“Windows Installer (.msi)”或“Windows Binary (.exe)”进行下载。 双击下载的.msi或.exe文件进行安装。 在cmd中输入node --version和npm --version&…

MySQL为root用户添加IP地址连接权限

需求 部署在本地的MySQL数据库&#xff0c;默认主机是localhost&#xff0c;这样局域网内的其他电脑就会连接不到&#xff0c;如果想要其他的电脑也可以访问的话&#xff0c;需要将主机名设置为本机IP。 解决方案 MySQL语句&#xff1a; GRANT ALL PRIVILEGES ON *.* TO 用户…

numpy数组transpose方法的基本原理

背景&#xff1a;记录一下numpy数组维度顺序操作 一、具体示例 transpose方法用于交换数组的轴&#xff0c;改变数组的维度顺序。方法的参数是一个代表新轴顺序的元组。 假设你有一个三维数组&#xff0c;其形状是 (a, b, c)&#xff0c;即有 a 个块&#xff0c;每个块中有 b…