一文带你弄懂 JVM 三色标记算法

2023-11-02

最近和一个朋友聊天,他问了我 JVM 的三色标记算法。我脑袋一愣发现竟然完全不知道!于是我带着疑问去网上看了几天的资料,终于搞清楚啥事三色标记算法,它是用来干嘛的,以及它和 CMS 回收器和 G1 回收器的关系了。今天,就让树哥带着大家一起盘一盘它!

根可达算法

我们要进行垃圾回收,就需要弄明白哪些对象是需要回收的,哪些对象是不需要回收的。针对这个问题,其实业界已经有几种常见的解决方法了。

第一种是计数法,就是每个对象都有一个计数器,被引用了加一,移除引用减一。但这种方法比较麻烦,而且也会有循环依赖的问题,因此并不被广泛使用。第二种是根可达算法,即以 GCRoots 为基础去扫描整个引用链,从而找到所有的可达对象,那剩下的其他对象就是不可达的垃圾对象了。

现在被广泛使用的是第二种算法,即根可达算法。

那怎么去实现根可达算法呢?

最简单的一种实现方案是:从GCRoots 节点开始,使用「标记-清除」算法去实现。

这种实现方案分为两个阶段,分别是:标记阶段、清除阶段。在标记阶段,它从 GCRoots 节点开始扫描整个引用链,找到所有可达的对象。在清除阶段,扫描整个引用链的不可达对象,然后将垃圾对象清除掉。整个算法实现过程如下图所示。

但这种方式有一个很大的缺点:整个过程必须「Stop the World」。这就导致整个应用程序必须停止,不能做任何改变,这是非常不友好的。CMS 回收器出现之前的所有回收器,都是用这种方式实现的,因此 GC 停顿时间都比轿长。

三色标记算法

为了解决上面「标记-清除」算法的问题,于是就出现了「三色标记算法」!

三色标记算法指的是将所有对象分为白色、黑色和灰色三种类型。黑色表示从 GCRoots 开始,已扫描过它全部引用的对象,灰色指的是扫描过对象本身,还没完全扫描过它全部引用的对象,白色指的是还没扫描过的对象。

但仅仅将对象划分成三个颜色还不够,真正关键的是: 实现根可达算法的时候,将整个过程拆分成了初始标记、并发标记、重新标记、并发清除四个阶段。

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

对比一下「四阶段拆分」和「一段式」的实现方式,我们可以看出: 通过将最耗时的引用链扫描剥离出来作为并发标记阶段,将其与用户线程并发执行,从而极大地降低了 GC 停顿时间。 但 GC 线程与用户线程并发执行,会带来新的问题:对象引用关系可能会发生变化,有可能发生多标和漏标问题。

多标与漏标问题

多标问题指的是原本应该回收的对象,被多余地标记为黑色存活对象,从而导致该垃圾对象没有被回收。多标问题会出现,是因为在并发标记阶段,有可能之前已经被标记为存活的对象,其引用被删除,从而变成了不可达对象。例如下图中,假设我们现在遍历到了节点 E,此时应用执行了 objD.fieldE = null; 。那么此刻之后,对象 E、F、G 应该是被回收的。但因为节点 E 已经是灰色的,那么 E、F、G 节点都会被标记为存活的黑色状态,并不会被回收。

多标问题会导致内存产生浮动垃圾,但好在其可以再下次 GC 的时候被回收,因此问题还不算很严重。

漏标问题指的是原本应该被标记为存活的对象,被遗漏标记为黑色,从而导致该垃圾对象被错误回收。例如下图中,假设我们现在遍历到了节点 E,此时应用执行如下代码。这时候因为 E 对象没有引用了 G 对象,因此扫描 E 对象的时候并不会将 G 对象标记为黑色存活状态。但由于用户线程的 D 对象引用了 G 对象,这时候 G 对象应该是存活的,应该标记为黑色。但由于 D 对象已经被扫描过了,不会再次扫描,因此 G 对象就被漏标了。

var G = objE.fieldG; 
objE.fieldG = null;  // 灰色E 断开引用 白色G 
objD.fieldG = G;  // 黑色D 引用 白色G

漏标问题就非常严重了,其会导致存活对象被回收,会严重影响程序功能。

那么我们的垃圾回收器是怎么解决这个问题的呢?

答案是: 增加一个「重新标记」阶段。无论是在 CMS 回收器还是 G1 回收器,它们都在并发标记阶段之后,新增了一个「重新标记」阶段来校正「并发标记」阶段出现的问题。 只是对于 CMS 回收器和 G1 回收器来说,它们解决的原理不同罢了。

漏标解决方案

正如前面所说,三色标记算法会造成漏标和多标问题。但多标问题相对不是那么严重,而漏标问题才是最严重的。我们经过分析可以知道,漏标问题要发生需要满足如下两个充要条件:

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

只有当上面两个条件都满足,三色标记算法才会发生漏标的问题。换言之,如果我们破坏任何一个条件,这个白色对象就不会被漏标。 这其实就产生了两种方式,分别是:增量更新、原始快照。CMS 回收器使用的增量更新方案,G1 采用的是原始快照方案。

CMS 解决方案

CMS 回收器采用的是增量更新方案,即破坏第一个条件:「有至少一个黑色对象在自己被标记之后指向了这个白色对象」。

既然有黑色对象在自己标记后,又重新指向了白色对象。那么我就把这个黑色对象的引用记录下来,在后续「重新标记」阶段再以这个黑色对象为跟,对其引用进行重新扫描。通过这种方式,被黑色对象引用的白色对象就会变成灰色,从而变为存活状态。

这种方式有个缺点,就是会重新扫描新增的这部分黑色对象,会浪费多一些时间。但是这段时间相对于并发标记整个链路的扫描,还是小巫见大巫,毕竟真正发生引用变化的黑色对象是比较少的。

G1 解决方案

G1 回收器采用的是原始快照的方案,即破坏第二个条件:「所有的灰色对象在自己引用扫描完成之前删除了对白色对象的引用」。

既然灰色对象在扫描完成后删除了对白色对象的引用,那么我是否能在灰色对象取消引用之前,先将灰色对象引用的白色对象记录下来。随后在「重新标记」阶段再以白色对象为根,对它的引用进行扫描,从而避免了漏标的问题。通过这种方式,原本漏标的对象就会被重新扫描变成灰色,从而变为存活状态。

这种方式有个缺点,就是会产生浮动垃圾。因为当用户线程取消引用的时候,有可能是真的取消引用,对应的对象是真的要回收掉的。这时候我们通过这种方式,就会把本该回收的对象又复活了,从而导致出现浮动垃圾。但相对于本该存活的对象被回收,这个代码还是可以接受的,毕竟在下次 GC 的时候就可以回收了。

对于 CMS 和 G1 这两种处理方案哪种更好,很多资料说的是 G1 这种解决方案更好。原因是其觉得 G1 这种方式产生了一些浮动垃圾,但节省了一些时间。但我对比了一下发现:CMS 和 G1 都需要重新对某些元素进行引用链扫描。从这点看来,好像差别不大。有弄懂的朋友可以评论区留言讨论讨论。

总结

看完了整篇文章,我们试图来回答一些问题。

三色标记算法是什么?三色标记算法是根可达算法的一种实现方案,其目的是为了找出所有可达对象。

为什么要有三色标记算法?因为传统的「标记-清除」算法效率太低,于是采用三色标记算法通过将对象分成白色、黑色、灰色,以及将整个过程拆分成「初始标记、并发标记、重新标记、并发清除」4 个过程,从而降低 GC 停顿时间。

三色标记算法有什么缺陷?三色标记算法会产生多标和漏标问题,其中漏标问题最严重。漏标问题会导致本该存活的对象被回收,从而导致严重的程序问题。

漏标有什么解决方案?漏标有两种解决方案,分别是:增量更新和原始快照方式。CMS 回收器采用了增量更新方式,G1 回收器采用了原始快照方式。

漏标哪种解决方案最好?江湖传闻 G1 回收器的原始快照方式效率高,但没有确切的理论证明,且听且珍惜。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一文带你弄懂 JVM 三色标记算法 的相关文章

  • 如何使用固定数量的工作线程实现简单线程

    我正在寻找最简单 最直接的方法来实现以下内容 主程序实例化worker 执行任务的线程 Only n任务可以同时运行 When n已达到 不再有工人 开始直到计数 正在运行的线程回落到下方n 我觉得Executors newFixedThr
  • 在Java中将*s打印为三角形?

    我在 Java 课程中的作业是制作 3 个三角形 一份左对齐 一份右对齐 一份居中 我必须为什么类型的三角形制作一个菜单 然后输入需要多少行 三角形必须看起来像这样 到目前为止 我能够完成左对齐的三角形 但我似乎无法获得其他两个 我尝试用谷
  • Java:线程“主”中的异常 java.lang.StringIndexOutOfBoundsException:字符串索引超出范围:

    我是初学者 谁能帮我弄清楚我们在做什么 我正在尝试读取字符串并将字符串的每个字符存储在数组中 import java util Scanner public class CoreMainDigitExtractor static Scann
  • 如何将 JVM 选项传递给 SBT 以在运行应用程序或测试用例时使用?

    我想在运行我的应用程序或通过 SBT 对应用程序进行测试时指定 JVM 选项 具体来说 我需要能够为 JVM 提供 Djava security policy 参数 以便加载我的策略并用于测试 我怎样才能用 SBT 做到这一点 With x
  • 检查 IPv4 地址是否在私有范围内

    在 Python 中 使用 IPy 模块您可以执行以下操作 gt gt gt ip iptype PRIVATE 有没有一个库或简单的方法可以在 Java 中执行相同的操作 似乎不完全是但是InetAddress有一些 isXX 方法 例如
  • java中的单链表和双向链表?

    在java中 哪个集合接口可以有效地实现单链表和双向链表 请问代码示例吗 毫不奇怪 实现双向链表的正确接口是 LinkedList 看Java文档 http docs oracle com javase 8 docs api java ut
  • Kafka Java Consumer 已关闭

    我刚刚开始使用卡夫卡 我面临着消费者的一个小问题 我用Java写了一个消费者 我收到此异常 IllegalStateException 此消费者已关闭 我在以下行中遇到异常 ConsumerRecords
  • 使用 JAX-WS 的 WebLogic 中没有模式导入的单个 WSDL

    如何使用 JAX WS 配置由 WebLogic 10 3 6 生成的 Web 服务 以将对象架构包含在单个 WSDL 文件声明 而不是导入声明 中 示例代码 界面 import javax ejb Local Local public i
  • Java - JPanel 内有边距和 JTextArea

    我想创建这样的东西 主面板有其边距 x 并且 TextArea 位于该面板的中心 几乎填满了面板 底部是另一个具有自定义尺寸 高度 y 的面板 可以使用某些快捷方式将其切换为可见和不可见 底部面板有 FlowLayout 和几个元素 问题是
  • LocalDate 减去 period 得到错误的结果

    LocalDate减去一个Period 如 28年1个月27天 得到错误的结果 但减去一个Period 只有天单位 如 10282 天 得到正确的结果 有什么需要注意的吗 public static void main String arg
  • 是否有最新的 Facebook Java SDK? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 好像没找到最近更新的 如果没有 是否有一个好的 Java 库来执行与 Facebook 的 API 交
  • 从 Android 访问云存储

    我一直无法找到任何有关如何从 Android 应用程序使用云存储的具体文档 我确实遇到过这个客户端库 https cloud google com storage docs reference libraries然而 Google Clou
  • 改变 Java 中凯撒移位的方向

    用户可以通过选择 1 向左或 2 向右移动字母来选择向左或向右移动 左边工作正常 右边不行 现在它显示了完全相同的循环 但我已经改变了所有 and 以不同的方式进行标记 最终我总是得到奇怪的字符 如何让程序将字符向相反方向移动 如果用户输入
  • JAXB 编组器无参数默认构造函数

    我想从 java 库中编组一个 java 对象 当使用 JAXB marschaller 编组 java 对象时 我遇到了一个问题 A 类没有无参数默认构造函数 我使用Java Decompiler来检查类的实现 它是这样的 public
  • Java8:流映射同一流中的两个属性

    我有课Model带有以下签名 class Model private String stringA private String stringB public Model String stringA String stringB this
  • Java和手动执行finalize

    如果我打电话finalize 在我的程序代码中的一个对象上 JVM当垃圾收集器处理这个对象时仍然再次运行该方法吗 这是一个大概的例子 MyObject m new MyObject m finalize m null System gc 是
  • Java 中处理异步响应的设计模式

    我读过类似问答的答案 如何在 JAVA 中创建异步 HTTP 请求 https stackoverflow com questions 3142915 how do you create an asynchronous http reque
  • java中的预增量/后增量

    有人可以帮助我理解为什么 int i 1 int j 1 int k 1 int l 1 System out println i i System out println j j System out println k k System
  • Java 中的微分方程

    我正在尝试用java创建一个简单的SIR流行病模型模拟程序 基本上 SIR 由三个微分方程组定义 S t l t S t I t l t S t g t I t R t g t I t S 易感人群 I 感染人群 R 康复人群 l t c
  • 将数组值导出到 csv 文件 java

    我只需要帮助将数组元素导出到 csv 文件 我不知道我的代码有什么问题 任何帮助将不胜感激 谢谢 for int index 0 index lt cols length index FileWriter fw new FileWriter

随机推荐

  • NVIDIA GeForce Experience登录报错:验证程序加载失败,请检查您的浏览器设置,例如广告拦截程序(解决办法)

    NVIDIA GeForce Experience登录报错 验证程序加载失败 请检查您的浏览器设置 例如广告拦截程序 解决办法 解决结果 点击驱动程序进行检查跟新 解决问题办法 1 打开控制面板 选择 网络和共享中心 2 选择 更改适配器设
  • deque用法详解

    无意中发现了一个巨牛的人工智能教程 忍不住分享一下给大家 教程不仅是零基础 通俗易懂 而且非常风趣幽默 像看小说一样 觉得太牛了 所以分享给大家 点这里可以跳转到教程 deque函数 deque容器为一个给定类型的元素进行线性处理 像向量一
  • 编译安装Nginx

    安装make yum y install gcc automake autoconf libtool make 安装g yum y install gcc gcc c PCRE库 Nginx需要PCRE Perl Compatible Re
  • Dotween运动曲线与路径动画

    Dotween运动曲线与路径动画 Dotween 运动曲线 内置的运动曲线 AnimationCurve Dotween 路径动画 一 设置一个数组存放位置坐标 二 直接写出自己想要到的坐标 Dotween 运动曲线 想要理解Dotwenn
  • Spring Boot之容器功能

    目录 一 Spring 注入组件的注解 二 Configuration 1 代码演示 1 1JavaBean Monster java 1 2配置类 1 3执行代码 2 Configuration 注意事项和细节 三 Import 1 创建
  • 1380. 矩阵中的幸运数

    class Solution public vector
  • oracle聚合函数

    1 COUNT 计算元组的个数 2 COUNT DISTINCT ALL col 对一列中的值计算个数 distinct去重复 缺省时是ALL 3 SUM DISTINCT ALL lt 列名 gt 求某一列值的总和 数值型 4 AVG D
  • 知道创宇研发技能列表v3.0

    Expand Collapse 知道创宇研发技能表v3 0 2015 8 21 发布 by 知道创宇 www knownsec com 余弦 404团队 后续动态请关注微信公众号 Lazy Thought 说明 关于知道创宇 知行合一 守正
  • go语言入门详细教程

    文章目录 一 前言 1 Go语言的创始人 2 go语言的发展 3 go语言优缺点 4 使用go语言的项目 5 学习go语言可以做什么 一 前言 1 Go语言的创始人 Go 语言的创始人是 Robert Griesemer Rob Pike
  • 全球第二大成人网站,也要“自宫”了。。

    兄弟们 一直以全球第二大成人网站自居的O站 全称 OnlyFans 可能又要搞事情了 众所周知 这个O站一直都是一个有梦想的成人网站 他们的目标从来都不只是单纯的做大做强 它一直都没有放弃过 想要上市的 梦想 只不过吧 成人网站想要上市 这
  • 调试cube生成的f107+lan8720代码

    之前用的w5500 无奈芯片越来越贵了 正好手头上有100来颗lan8720a 直接将方案改了吧 以前在深圳工作时公司的网关正好用的这个方案 直接抄吧 硬件设计网口无晶振 由mcu的mco脚输出 50Mhz模式 其他都是通用连接方式 接下来
  • ubuntu设置ssh登陆

    默认请况下 ubuntu是不允许远程登陆的 因为服务没有开 可以这么理解 想要用ssh登陆的话 要在需要登陆的系统上启动服务 即 安装ssh的服务器端 sudo apt get install openssh server 然后 启动服务
  • graphviz安装及使用、决策树生成

    一 graphviz下载安装 下载网址 http www graphviz org download 选择合适版本下载 1 1 双击安装 1 2 点击下一步 1 3 点击我接受 1 4 添加至系统路径 勾选添加至当前用户的系统路径 创建桌面
  • 诛仙服务器获取角色信息失败,架设诛仙提示游戏服务器正在维护中

    架设诛仙提示游戏服务器正在维护中 内容精选 换一换 一 系统信息相关命令本节内容主要是为了方便通过远程终端维护服务器时 查看服务器上当前 系统日期和时间 磁盘空间占用情况 程序执行情况本小结学习的终端命令基本都是查询命令 通过这些命令对系统
  • Interactive Image Segmentation

    FocalClick Towards Practical Interactive Image Segmentation 阿里巴巴 CVPR2022 Interactive segmentation allows users to extra
  • 大学物理绝不挂科期末考试复习

    大学物理 第一章走近物理 第二章 质点运动学 三角形法则 矢量平移不变性 V V0 at X V0t 1 2a t 2 变速运动 积分 建立自然坐标系比较好 微分积分 你
  • sobol灵敏度分析matlab_sobol全局灵敏性分析

    最近在研究全局敏感性分析方法中的 Sobol 方法 看了一些国内的论文 发现一个通病 就是公 式一挂就可以得出结果了 真心觉得这种论文很 恶心 主要原因是自己看不太懂 直到在维基百 科上面找到了这种方法的详细解释 今天我们就根据网页上的步骤
  • Sqli-Labs Less1-16关介绍

    Sqli Labs Less1 16关介绍 一 Http 请求方法 Get 对比 Post Get传输方式 Less1 10 Less1 4 Union Select注入 Less5 6 报错型注入 Less 7 写入数据 闭合符 Less
  • 设计模式学习(五):State状态模式

    目录 一 什么是State模式 二 State模式示例程序 2 1 伪代码 2 1 1 不使用State模式的伪代码 2 1 2 使用State模式的伪代码 2 2 各个类之间的关系 2 3 State接口 2 4 DayState类 2
  • 一文带你弄懂 JVM 三色标记算法

    最近和一个朋友聊天 他问了我 JVM 的三色标记算法 我脑袋一愣发现竟然完全不知道 于是我带着疑问去网上看了几天的资料 终于搞清楚啥事三色标记算法 它是用来干嘛的 以及它和 CMS 回收器和 G1 回收器的关系了 今天 就让树哥带着大家一起