垃圾回收简称 GC,就是对程序中不再使用的内存资源进行自动回收释放的操作。

常见的垃圾回收算法

引用计数:每个对象维护一个 引用计数,当对象被创建或被其他对象引用时,计数 +1;如果引用该对象的对象被销毁时,计数 -1 ;当计数为 0 时,回收该对象。

  • 优点:对象可以很快被回收,不会出现内存耗尽或到达阀值才回收。
  • 缺点:不能很好的处理循环引用;需要实时维护计数引用。

标记-清除:从根变量开始遍历所有引用的对象,引用的对象会被标记,没有被标记的则进行回收。

  • 优点:解决了引用计数的缺点;
  • 缺点:需要 STW(stop the world),暂时停止程序运行。

分代收集:按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,短的放入新生代,不同代有不同的回收算法和回收频率。

  • 优点:回收性能好;
  • 缺点:算法复杂。

go1.3 版本之前使用的是 标记-清除 算法,但是执行该算法时,需要停掉用户程序(STW),即保证 GC 和 用户程序 是串行 的。这种算法简单,但是会严重影响用户程序的执行。

go1.5 版本开始后提出了 三色标记法,它优化了标记过程,同时结合 屏障技术 极大的缩短了 STW 的时间,让 用户程序 和 GC 在 标记和清除阶段 可以并行运行。

三色标记法

三色抽象

三色抽象定义了三种不同类型的对象,并用不同的颜色相称:

  • 白色:未搜索的对象,在回收周期开始时所有对象都是白色,在回收周期结束时所有的白色都是垃圾对象
  • 灰色:正在搜索的对象,该类对象可能还存在外部引用对象
  • 黑色:已搜索完的对象,这类对象不再有外部引用对象

标记流程

1、初始状态下所有对象都是 白色 的;

2、从 根节点对象 开始遍历所有对象,把遍历到的对象变成 灰色,放入待处理队列;

3、遍历所有灰色对象,将遍历到的灰色对象变成 黑色,同时将它引用的对象变成灰色并加入到待处理队列;

4、循环步骤3,直到 待处理队列为空(所有灰色对象都变为黑色);

5、剩下没有被标记的 白色 对象就认为是垃圾对象。

其中,根节点对象 主要指执行 GC 时刻所有的 全局对象栈上的对象(函数参数与内部变量)。

并发问题:

如果仅按以上的标记清除过程处理,在没有用户程序并发修改对象引用的情况下,回收可以正常结束。但如果用户态程序在标记阶段更新了对象引用关系,就可能会导致问题的出现:

用以上过程为例,在 A 对象已经被标记为 黑色 后,用户程序修改了引用关系,将 A 对象 引用到 白色对象 E,而此时 A对象 已经被标记为黑色,gc 扫描不会再对它进行处理,最后,E 对象就会被错误的清除。

怎么防止以上问题的出现呢?

首先,出现该问题是因为在 GC 执行的时候,用户程序并发的修改了对象引用关系,而且,修改的引用关系 同时满足 以下两个条件:

  1. 黑色对象引用了白色对象;
  2. 该白色对象没有被其他灰色对象引用 或者 灰色对象与它之间的可达关系遭到破坏。

解决方法是实现以下 三色不变式:

  • 强三色不变式:黑色节点不允许引用白色节点,破坏了条件一。
  • 弱三色不变式:黑色节点允许引用白色节点,但是该白色节点必须有其他灰色节点的引用或间接引用,破坏了条件二。

怎么实现呢?

一个直观的想法就是在用户程序修改对象引用关系的时候,在相关对象上做点手脚,以此来破坏以上 2 个条件同时产生,这其实就是 写屏障 干的事。

写屏障是指 编译器在编译期间生成一段代码,该代码可以拦截用户程序的内存读写操作,在用户程序操作之前执行一个 hook 函数,根据 hook 函数的不同,分为 Dijkstra 插入写屏障Yuasa 删除写屏障

需要注意的是,基于对栈空间实现写屏障产生的性能损耗和实现复杂度的考虑,go 没有对 栈空间对象 使用写屏障。

写屏障(Write Barrier)

Dijistra 插入写屏障

// 当前下游对象 slot, 新下游对象 ptr
func writePointer(slot *unsafe.Pointer, ptr unsafe.Pointer) {shade(ptr); // 将新引用对象 ptr 标记为灰色*slot = ptr;
}

堆对象 增加引用对象的时候,先把该引用对象置为 灰色,这样就可以保证不会有黑色对象引用白色对象,满足 强三色不变式

但是因为栈空间对象是没有写屏障的,因此,在标记过程中,可能出现 黑色的栈对象 引用到 白色对象 的情况,所以在一轮三色标记完成后 需要开启 STW,重新对 栈上的对象 进行三色标记。

图解:

在 A 、C 对象已经被标记为 黑色 后,用户程序执行 A.ref = E , C.ref = G ,

因为 A 属于栈对象, 因此不会对它新增的引用对象做处理, 因此,E 为 白色

而 C 属于堆对象,按照屏障函数的处理,要将它引用的对象 G 着为 灰色

本轮标记结束后,E 对象保持为白色,因此,还要开启 STW,对 栈区 重新做一次三色标记

Yuasa 删除写屏障

// 当前下游对象 slot, 新下游对象 ptr
func writePointer(slot *unsafe.Pointer, ptr unsafe.Pointer) {shade(*slot); // 将当前引用对象 *slot 标记为灰色*slot = ptr;
}

在删除某对象的引用对象时,针对 被删除的对象,如果自身为灰色,则不用处理,如果为白色,那么将该对象标记为灰色,满足 弱三色不变式

图解:

1、A 、D 对象为 黑色,B、E 为 灰色 ,其他对象为白色;

2、用户程序执行 B.ref = nil , E.ref = C,按照屏障函数处理:删除对白色对象 的引用,要将白色对象着为灰色,因此执行 B.ref = nil 要将 C 对象被着为 灰色; F 同理;

3、继续三色标记;

4、最后 F 和 G 对象 也会被标记为黑色而保留至下一轮,虽然它们已然可以被清理。

可以看到,删除写屏障有两个缺点:

  1. 回收精度低:因为该方法保守的认为所有被删除的节点将来可能会被黑色节点引用,因此在删除的时候要将其置为灰色,而这个节点可能再也不会被其他节点引用,从而导致该节点以及它引用下的其他节点都在本轮 gc 后被保留下来,要待到下轮 gc 才可以被清除。
  2. 必须在 gc 开启时执行 STW,扫描根集合,保证所有堆上在用的对象要么为灰色,要么处于灰色保护下,即保证 根黑,下一级在堆上的全灰。

第 2 点 讲的是什么鬼,首先 举个反例:

A、B 属于两个不同的协程栈对象,假如现在不在 gc 开始前扫描整个栈区,可能出现:

1、扫描 g1 栈对象,执行三色标记,将 A、C 标记为黑色后;

2、扫描 g2 栈对象,将 B 标记为灰色,然后执行用户程序代码 B.C.ref = D, B.ref = nil因为 栈区没有实现写屏障,因此,B 删除 对 D 的引用后 D仍然为白色;

3、继续三色标记,因为 C 已经为黑色对象,不会再被扫描,导致最后 D、G 始终保持为白色而被删除。

再来看下 正确的过程

先扫描根集合,使 A、B 栈节点都变为黑色,C、D 堆节点变为灰色,堆区其他所有节点都处于灰色节点的可达链路上,这样就可以保证以后即使 B 删除了对 D 对象的引用后,G 对象也不会被清除。

正反例都举完了,你可能还存在一个疑问,在执行过程中,如果用户代码 new 了一个对象,并且被某个黑色节点引用,比如, A.ref = new(F),  那这个新构建的对象不是也会被误删。

其实,这个时候我们回过头来想想,这个 F 对象如果没有发生逃逸,它会是一个栈对象,那在 gc 开始时就会被扫黑,如果这个 F 对象发生了逃逸,是一个堆对象,那么它也会因为 A 被扫黑的过程中成为了一个灰色对象。

感觉有点废话,但是我曾经脑补过这场景,想了好久才想明白。

混合写屏障

回顾一下,单独的写屏障都有各自的缺陷:

插入写屏障 需要在一轮标记结束后执行 STW,重新对栈区执行一次扫描标记;

删除写屏障 需要在 gc 开始阶段扫描整个栈,生成起始快照。

在 go 1.8 版本引入了 混合写屏障技术,集两种写屏障各自的优点于一身。

// 当前下游对象 slot, 新下游对象 ptr
writePointer(slot, ptr){shade(*slot)  // 对应删除写屏障,将当前引用对象 *slot 标记为灰色shade(ptr)    // 对应插入写屏障,将新引用对象 ptr 标记为灰色*slot = ptr
} 

针对 栈区 和 堆区 分别采用以下策略处理:

栈区:

栈上对象全部扫描标记为黑色(每个栈单独扫描,无需 STW 整个程序,停止单个扫描栈即可);

GC 期间,任何在栈上创建的新对象,均为黑色(不用再对栈重新扫描);

堆区:

被删除的对象标记为灰色(删除写屏障);

被添加的对象标记为灰色(插入写屏障)。

gc 触发时机

gcTriggerHeap:堆内存的分配达到控制器计算的触发堆大小,初始大小环境变量 GOGC,之后堆内存达到上一次垃圾收集的 2 倍时才会触发GC;

gcTriggerTime:距离上一次垃圾回收超过一定阈值时,该时间由 runtime.forcegcperiod 变量控制,默认为 2 分钟。

gcTriggerCycle: 要求启动新一轮的GC, 已启动则跳过, 手动触发GC的runtime.GC()会使用这个条件。

golang 垃圾回收、三色标记法、写屏障相关推荐

  1. JVM垃圾回收——三色标记法

    目录 一.什么是三色标记 二.三色标记的过程 三.三色标记的缺点 四.垃圾回收机如何弥补三色标记的缺点 在CMS.G1这种并发的垃圾收集器收集对象时,假如一个对象A被GC线程标记为不可达对象,但是用户 ...

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

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

  3. 【七天入门Go语言】 GC垃圾回收三色标记 | 第七天

    目录 GC 三色标记法 最后 GC GC全称Garbage Collection 目前主流的垃圾回收算法有两类,分别是追踪式垃圾回收算法(Tracing garbage collection)和引用计 ...

  4. 内存管理篇(三):Go垃圾回收之三色标记算法

    三色标记法介绍: 三色标记法(tricolor mark-and-sweep algorithm)是传统 Mark-Sweep 的一个改进,它是一个并发的 GC 算法,在Golang中被用作垃圾回收的 ...

  5. golang 所有进程休眠_golang 垃圾回收(三)插入写屏障

    并发的垃圾回收 STW 安全的回收 并发的垃圾回收 插入写屏障 伪代码 对象丢失的必要条件 写屏障是怎么解决问题? 并发的垃圾回收 golang 语言设计的根本性追求就是高并发,低延迟,所以golan ...

  6. 双/三色标记法的垃圾回收(GC)原理解析和缺陷解决方案(Go,Lua以及jvm的CMS和G1垃圾回收器中使用的回收算法)

    标记-清除算法 go和lua虚拟机以及jvm的CMS和G1垃圾回收器的回收算法的思想均来自于标记-清除算法(Mark-Sweep),它们的gc有重要的两部分: 1.从根节点遍历所有对象,如果可达到,则 ...

  7. 浅谈垃圾对象回收之《三色标记法》

    文章目录 前言 什么是垃圾回收 JAVA的垃圾回收回顾 GO的垃圾回收学习 **三色标记法(tricolor mark-and-sweep algorithm)** Dijkstra方法(插入屏障,强 ...

  8. 【JVM】三色标记法与读写屏障

    1.概述 首先:CMS和G1都使用了三色标记法 关于垃圾回收算法,基本就是那么几种:标记-清除.标记-复制.标记-整理.在此基础上可以增加分代(新生代/老年代),每代采取不同的回收算法,以提高整体的分 ...

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

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

最新文章

  1. Linux wait() 和 waitpid()函数介绍
  2. 乐在其中设计模式(C#) - 中介者模式(Mediator Pattern)
  3. 秒懂 QPS、TPS、PV、UV、GMV、IP、RPS!
  4. sqoop mysql 安装_Sqoop安装及验证_MySQL
  5. web实现数据交互的几种常见方式
  6. java实现k-means算法(用的鸢尾花iris的数据集,从mysq数据库中读取数据)
  7. 终于有人把超融合和边缘计算说清楚了
  8. 第二阶段冲刺-个人总结04
  9. 初学Laravel框架与ThinkPHP框架的不同
  10. 2014年的暑假ACM之旅!
  11. Elasticsearch实战(十四)---聚合搜索Aggs多层嵌套聚合处理
  12. Excel 查重小技巧,适用于office2003
  13. 【c语言】—求最大公约数和最小公倍数多种方法
  14. Pytorch 深度强化学习模型训练速度慢
  15. 鸡年关于鸡的一些歇后语
  16. 迁移Veil:手工打造Windows下编译的免杀Payload
  17. WLAN适配器的驱动程序可能出现了问题(连不了wifi)解决办法
  18. 网件交换机 trunk_如何轻松放置Netgear Arlo摄像机
  19. [系统安全]目前修复系统漏洞最好的中文软件
  20. java项目文件_访问Java项目中的文件

热门文章

  1. 汽车ECU通信相关验证项有哪些?
  2. Luck 的象棋(深度优先搜索、宽度优先搜索)象棋棋子“马”走日,求最短步数
  3. demo代码目录整理
  4. 2021丹东二中高考14班成绩查询,2021年丹东高考状元名单公布,丹东高考状元学校资料及最高分...
  5. html单选按钮值的读取,js简单获取表单中单选按钮值的方法
  6. Outlook2007备份的方法
  7. 一声令下即可关灯,再也不用在寒冬深夜离开被窝~
  8. 空间三角形_对应三种空间形状的三种几何
  9. linux播放器切换,让mplayer成为linux下的万能播放器(更新)
  10. Java算法题目小记3:勾股定理,西方称为毕达哥拉斯定理,它所对应的三角形现在称为:直角三角形.已知直角三角形的斜边是某个整数,并且要求另外两条边也必须是整数。 求满足这个条件的不同直角三角形的个数。