垃圾回收之三色标记法(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、漏标问题

漏标的问题满足两个条件:
- 有至少一个黑色对象在自己被标记之后指向了这个白色对象
- 所有的灰色对象在自己引用扫描完成之前删除了对白色对象的引用
CMS如何解决漏标问题---写屏障+增量更新方式
G1如何解决漏标问题---原始快照方式
总结:
对于读写屏障,以Java HotSpot VM为例,其并发标记时对漏标的处理方案如下:
- CMS:采用的是写屏障 + 增量更新
- G1: 采用的是写屏障 + 原汁快照(SATB)
- ZGC:采用的是读屏障
CMS收集器解决漏标问题:增量方式 如果现在B(黑色)对象引入白色对象,写屏障。
好处:避免浮动垃圾,缺点扫描整个引用链效率比较低。
G1收集器解决漏标问题:原始快照方式。
好处:效率非常高,无需扫描整个引用链,缺点:可能会产生浮动垃圾。
参考资料:
https://en.wikipedia.org/wiki/Tracing_garbage_collection
https://www.cnblogs.com/jmcui/p/14165601.html
垃圾回收之三色标记法(Tri-color Marking)的更多相关文章
- Javaer 面试必背系列!超高频八股之三色标记法
可达性分析可以分成两个阶段 根节点枚举 从根节点开始遍历对象图 前文提到过,在可达性分析中,第一阶段 "根节点枚举" 是必须 STW 的,不然如果分析过程中用户进程还在运行,就可能 ...
- JVM垃圾回收之三色标记
三色标记法是一种垃圾回收法,它可以让JVM不发生或仅短时间发生STW(Stop The World),从而达到清除JVM内存垃圾的目的.JVM中的CMS.G1垃圾回收器所使用垃圾回收算法即为三色标记法 ...
- [Java] 理解JVM之三:垃圾回收机制
JVM内存中的各个区域都会回收吗? 首先我们知道 Java 栈和本地方法栈在方法执行完成后对应的栈帧就立刻出栈销毁,两者的回收率可以认为是100%:Java 堆中的对象在没有被引用后,即使用完成后会被 ...
- 《深入理解Java虚拟机》第三章读书笔记(二)——HotSpot垃圾回收算法实现(OopMap,安全点安全区域,卡表,写屏障,三色标记算法)
系列文章目录和关于我 前面<深入理解Java虚拟机>第三章读书笔记(一)--垃圾回收算法我们学习了垃圾回收算法理论知识,下面我们关注下HotSpot垃圾回收算法的实现,分为以下几部分 对象 ...
- Golang垃圾回收机制(一)
原文: http://legendtkl.com/2017/04/28/golang-gc/ 1. Golang GC 发展 Golang 从第一个版本以来,GC 一直是大家诟病最多的.但是每一个版本 ...
- Golang——垃圾回收GC(2)
1 垃圾回收中的重要概念 1.1 定义 In computer science, garbage collection (GC) is a form of automatic memory manag ...
- 从golang的垃圾回收说起(上篇)
本文来自网易云社区 1 垃圾回收中的重要概念 1.1 定义 In computer science, garbage collection (GC) is a form of automatic me ...
- go 垃圾回收机制
转载一篇仔细分析了golang的垃圾回收策略以及发展的一篇文章 地址是https://mp.weixin.qq.com/s?__biz=MzAxNzMwOTQ0NA%3D%3D&mid=265 ...
- 内存屏障 WriteBarrier 垃圾回收 屏障技术
https://baike.baidu.com/item/内存屏障 内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之 ...
- 超详细的node/v8/js垃圾回收机制
前言 垃圾回收器是一把十足的双刃剑.其好处是可以大幅简化程序的内存管理代码,因为内存管理无需程序员来操作,由此也减少了(但没有根除)长时间运转的程序的内存泄漏.对于某些程序员来说,它甚至能够提升代码的 ...
随机推荐
- iOS 为 textView 添加 placeholder
OC : //自定义一个 placeholder 样式的 label UILabel *placeholder = [UILabel new]; placeholder.text = @"请 ...
- scala中的数据类型
一.Scala的数据类型 (1). scala中一切数据都是对象,都是Any的子类: (2).scala中数据类型分为两大类:数值类型(AnyVal,即any value,任意值).引用类型(AnyR ...
- 快速搭建基于webpack的babylon.js开发手脚架
原文:https://doc.babylonjs.com/how_to/page2 目录 创建一个项目 安装Babylon.js 设置webpack 安装依赖 配置webpack 插件 创建场景 设置 ...
- 根据id 删除树结构中的数据
根据id 删除树结构中的数据 filterHandle(data, id) { var newData = data.filter(x => x ...
- 1、Java程序概述
1.什么是Java? Java是一个完整的平台,有一个庞大的库,其中包含了很多可重用的代码,以及一个提供诸如安全性.跨操作系统的可移植性以及自动垃圾收集等服务的执行环境. 2.Java白皮书的关键术语 ...
- reflection反射
reflection反射 动态和静态语言 动态语言 动态语言就是一类在运行时可以改变其结构的语言,通俗点说就是在运行时代码可以根据某些条件改变自身结构 主要动态语言:object-C,C#,JavaS ...
- 关于nth-of-type的注意事项
普通使用 nth-of-type: <div class="box"> <div> 第一个元素 </div> <p>没有用的元素&l ...
- JS this指向相关
function Foo() { getName = function() { console.log(1) } return this;}Foo.getName = function() { con ...
- sqlserver 数据导入MySQL
sqlserver导出成Excel文件数据 为什么用Excel文件数据? sql文件不通用 CVS文件编码报错 text文件日期/时间戳报错 修改Excel文件中的日期字段 需要格式化日期字段为 yy ...
- node报错解决办法
依次报错: Error: Cannot find module 'gifsicle' Syntax Error: Error: Cannot find module 'imagemin-gifsicl ...