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

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

先看一下知识点导图:

一、如何标记

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

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

参考资料:

https://en.wikipedia.org/wiki/Tracing_garbage_collection

https://www.cnblogs.com/jmcui/p/14165601.html

垃圾回收之三色标记法(Tri-color Marking)的更多相关文章

  1. Javaer 面试必背系列!超高频八股之三色标记法

    可达性分析可以分成两个阶段 根节点枚举 从根节点开始遍历对象图 前文提到过,在可达性分析中,第一阶段 "根节点枚举" 是必须 STW 的,不然如果分析过程中用户进程还在运行,就可能 ...

  2. JVM垃圾回收之三色标记

    三色标记法是一种垃圾回收法,它可以让JVM不发生或仅短时间发生STW(Stop The World),从而达到清除JVM内存垃圾的目的.JVM中的CMS.G1垃圾回收器所使用垃圾回收算法即为三色标记法 ...

  3. [Java] 理解JVM之三:垃圾回收机制

    JVM内存中的各个区域都会回收吗? 首先我们知道 Java 栈和本地方法栈在方法执行完成后对应的栈帧就立刻出栈销毁,两者的回收率可以认为是100%:Java 堆中的对象在没有被引用后,即使用完成后会被 ...

  4. 《深入理解Java虚拟机》第三章读书笔记(二)——HotSpot垃圾回收算法实现(OopMap,安全点安全区域,卡表,写屏障,三色标记算法)

    系列文章目录和关于我 前面<深入理解Java虚拟机>第三章读书笔记(一)--垃圾回收算法我们学习了垃圾回收算法理论知识,下面我们关注下HotSpot垃圾回收算法的实现,分为以下几部分 对象 ...

  5. Golang垃圾回收机制(一)

    原文: http://legendtkl.com/2017/04/28/golang-gc/ 1. Golang GC 发展 Golang 从第一个版本以来,GC 一直是大家诟病最多的.但是每一个版本 ...

  6. Golang——垃圾回收GC(2)

    1 垃圾回收中的重要概念 1.1 定义 In computer science, garbage collection (GC) is a form of automatic memory manag ...

  7. 从golang的垃圾回收说起(上篇)

    本文来自网易云社区 1 垃圾回收中的重要概念 1.1 定义 In computer science, garbage collection (GC) is a form of automatic me ...

  8. go 垃圾回收机制

    转载一篇仔细分析了golang的垃圾回收策略以及发展的一篇文章 地址是https://mp.weixin.qq.com/s?__biz=MzAxNzMwOTQ0NA%3D%3D&mid=265 ...

  9. 内存屏障 WriteBarrier 垃圾回收 屏障技术

    https://baike.baidu.com/item/内存屏障 内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之 ...

  10. 超详细的node/v8/js垃圾回收机制

    前言 垃圾回收器是一把十足的双刃剑.其好处是可以大幅简化程序的内存管理代码,因为内存管理无需程序员来操作,由此也减少了(但没有根除)长时间运转的程序的内存泄漏.对于某些程序员来说,它甚至能够提升代码的 ...

随机推荐

  1. react+antd pro实现【列表可实时行内编辑】的弹窗表单组件

    纯列表版效果展示: ① 初始无值,展示为唤醒按钮+文案外链 ②点击按钮唤醒弹窗(简易版示意图) ③配置后 可编辑表格组件文档: https://procomponents.ant.design/com ...

  2. AX2012 快速清空整个log表数据

    如果当一个log表的数据非常大的时又需要清理时,如果允许删除全部数据,在AX里,可以 将log表的TableType调整为[TempDB], 保存同步后再将TableType设置回[Regular]即 ...

  3. uniapp 全局注册组件注意事项

    标准 根目录components  文件夹下建立 组件文件名文件夹 然后组件 autoscan  打开 别的用不到不写 全局使用 备注 因为不是vuecli 项目 只在H5 端生效  在app  上生 ...

  4. 微信小程序 css overflow :hidden 子元素不生效

    原css  .item .right {     width: 70%; }    .item .right .name {     font-size: 32rpx;     font-family ...

  5. Day1.无敌难受且成功

    Markdown学习 标题 字体 ctrl+b.l.u.k加粗.斜体.下划线.超链接 引用 大于号+空格 分割线 三个*** ###都 图片 !+[]+() 英文输入法的符号 输入的图片路径可本地也要 ...

  6. MySql数据库读取字段错误问题

    一个小小的BUG,断断续续搞了一个月才搞定,使用MySql的时候使用mysql_fetch_fields()获取的字段始终始终是错误的,因为是修改别人的代码,一直找不到问题,Debug了无数次还是搞不 ...

  7. 针对FILES和PATH的操作

    在修改漏洞的时候发现,根据建议都使用NIO包的FILES和PATH来进行文件操作,来保证安全性. import java.nio.file.Files;import java.nio.file.Pat ...

  8. .net core中使用HttpClient碰到的问题:This instance has already started one or more requests. Properties can only be modified before sending the first request

    项目里使用httpclient一般加staic或者单例来防止每次请求都会新建立一个连接,从而占用太多的服务器资源, 问题产生 但是今天新加的一个方法中每次需要请求不同的url,这时候就出现了错误: 就 ...

  9. svn操作方法

    1.SVN1.1.SVN概述1.1.1.为什么需要使用svn版本控制软件协作开发远程开发版本回退 1.1.2.解决之道SCM:软件配置管理所谓的软件配置管理实际就是对软件源代码进行控制与管理. CVS ...

  10. k8s namespace kubeDNS

    图中kube-dns只是一个service,但是他对外提供k8s集群内部的dns服务,真正的dns server,是 coredns这几个pod k8s namespace 的作用只是提供逻辑上的组件 ...