首页 > 其他分享 >垃圾回收之三色标记法

垃圾回收之三色标记法

时间:2023-04-04 10:25:06浏览次数:33  
标签:标记 对象 之三 线程 垃圾 GC 引用

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

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

一、如何标记

在 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。

 

1、基本算法

 

三色标记法将对象用三种颜色表示,分别是白色、灰色和黑色。

最开始所有对象都是白色的,然后把其中全局变量和函数栈里的对象置为灰色。

第二步把灰色的对象全部置为黑色,然后把原先灰色对象指向的变量都置为灰色,以此类推。

等发现没有对象可以被置为灰色时,所有的白色变量就一定是需要被清理的垃圾了。

 

 

 

三色标记法是一个 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:以浮动垃圾(占内存空间)换让我们用户线程能够暂停的时间更加短。

 

总结:

CMS收集器解决漏标问题:增量方式 如果现在B(黑色)对象引入白色对象,写屏障。

好处:避免浮动垃圾,缺点扫描整个引用链效率比较低。

 

G1收集器解决漏标问题:原始快照方式。

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

 

标签:标记,对象,之三,线程,垃圾,GC,引用
From: https://www.cnblogs.com/binyue/p/17282923.html

相关文章

  • 【pytest】 pytest自定义标记 PytestUnknownMarkWarning处理方式
    未注册标记会出现warningssummary-- PytestUnknownMarkWarningPytestUnknownMarkWarning:Unknownpytest.mark.demo-isthisatypo?Youcanregistercustommarkstoavoidthiswarning-fordetails,seehttps://docs.pytest.org/en/stable/how-to/mark.html@......
  • 垃圾回收之CardTable和Remembered Set
    JVM在进行垃圾收集的时候,有一项非常重要的工作就是确定这一次垃圾收集的对象到底有多少个,即确定liveset的范围。卡表和RSet(RememberSet),是JVM为了解决分代收集时,liveset扫描需要穿梭到不同的代的时候的效率问题。对于新生代垃圾收集器而言,这个问题又有其特殊之处。根据......
  • 论垃圾分类与边缘计算的关系
    小明是一位普通市民。每天,小明和邻居们一样,将生活垃圾混在一起,扔到小区的垃圾箱。然后,环卫人员进行收集运送,送到垃圾中转站。最后,再从垃圾中转站运到垃圾填埋场或焚烧厂,进行掩埋、焚烧。也有部分垃圾,会进行分拣处理再利用。为了保护环境,提高垃圾的价值,政府开始推行垃圾分类政策。政......
  • 三色标记法
    在遍历对象图的过程中,把需要遍历的对象按照“是否访问过”分为以下三种颜色。白色:表示对象尚未被垃圾回收器访问过。显然,在可达性分析刚刚开始的阶段,所有的对象都是白色......
  • 点击图片标记红点
    /**获取图片的xy坐标【点击】*/getMouseXY(e){console.log('dadada')//offset是点击当前对象【这里点击img】的偏移量this.imgX=......
  • 第三篇 作用域、作用域链、执行上下文、函数、内存泄漏和垃圾回收
    1、作用域作用域表示当前的执行上下文,值和表达式在其中可见或可被访问到的上下文。作用域决定了代码区块中变量和其他资源的可见性。1、全局作用域在代码中任何地......
  • Javascript之V8内存和垃圾回收讲解
    目录1Javascript内存1.1Javascript引擎1.2V8内存模型1.2.1栈1.2.2堆1.3内存生命周期1.3.1栈内对象生命周期1.3.2堆内对象生命周期2Javascript垃圾回收2.1引言2.2......
  • 三色标记法,又一八股知识点
    我们都知道,当JVM判断对象不再存活的时候,便会在下一次GC时候将该对象回收掉,为堆腾出空间,而JVM判断对象存活的算法大家比较熟知的有两种,分别是引用计数法和可达性分析算法引......
  • 将 Jenkins Pipeline 中的一个阶段标记为例如“不稳定”,但继续未来的阶段?
    stage('SignCode'){steps{script{try{pwd()sh"<YOURSCRIPTHERE>"}ca......
  • 树形表的标记字段是什么?如何查询树形表?
    树形表的标记字段是什么是parentID即父节点的id如何查询树形表当层级固定的时候可以用表的自连接查询select one.idone_id, one.labelone_label, two.idtwo_i......