golang垃圾回收
常见GC算法
我总结了一下常见的 GC 算法。分别是:引用计数法、Mark-Sweep法、三色标记法、分代收集法。
1. 引用计数法
原理是在每个对象内部维护一个整数值,叫做这个对象的引用计数,当对象被引用时引用计数加一,当对象不被引用时引用计数减一。当引用计数为 0 时,自动销毁对象。
目前引用计数法主要用在 c++ 标准库的 std::shared_ptr 、微软的 COM 、Objective-C 和 PHP 中。
但是引用计数法有个缺陷就是不能解决循环引用的问题。循环引用是指对象 A 和对象 B 互相持有对方的引用。这样两个对象的引用计数都不是 0 ,因此永远不能被收集。
另外的缺陷是,每次对象的赋值都要将引用计数加一,增加了消耗。
这种GC算法把业务代码与GC算法耦合在一起,GC会导致业务代码执行性能下降,变量指向变动越频繁,GC占用性能越高。
2. Mark-Sweep法(标记清除法)
这个算法分为两步,标记和清除。
- 标记:从程序的根节点开始, 递归地遍历所有对象,将能遍历到的对象打上标记。
- 清除:讲所有未标记的的对象当作垃圾销毁。
图片来自 https://en.wikipedia.org/wiki/Tracing_garbage_collection
但是这个算法也有一个缺陷,就是人们常常说的 STW 问题(Stop The World)。因为算法在标记时必须暂停整个程序,否则其他线程的代码可能会改变对象状态,从而可能把不应该回收的对象当做垃圾收集掉。
当程序中的对象逐渐增多时,递归遍历整个对象树会消耗很多的时间,在大型程序中这个时间可能会是毫秒级别的。让所有的用户等待几百毫秒的 GC 时间这是不能容忍的。
golang 1.5以前使用的这个算法。
3. 三色标记法
三色标记法是传统 Mark-Sweep 的一个改进,它是一个并发的 GC 算法。
原理如下,
- 首先创建三个集合:白、灰、黑。
- 将所有对象放入白色集合中。
- 然后从根节点开始遍历所有对象(注意这里并不递归遍历),把遍历到的对象从白色集合放入灰色集合。
- 之后遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合
- 重复 4 直到灰色中无任何对象
- 通过write-barrier检测对象有变化,重复以上操作
- 收集所有白色对象(垃圾)
图片来自 https://en.wikipedia.org/wiki/Tracing_garbage_collection
这个算法可以实现 "on-the-fly",也就是在程序执行的同时进行收集,并不需要暂停整个程序(后面会讲具体GC与业务代码怎么并发执行的,其实还是会有短暂的STW的)。
但是也会有一个缺陷,三色标记法是增量GC算法,可能程序中的垃圾产生的速度会大于垃圾收集的速度,这样会导致程序中的垃圾越来越多无法被收集掉。
使用这种算法的是 Go 1.5、Go 1.6。
4. 分代收集
分代收集也是传统 Mark-Sweep 的一个改进。这个算法是基于一个经验:绝大多数对象的生命周期都很短。所以按照对象的生命周期长短来进行分代。
一般 GC 都会分三代,在 java 中称之为新生代(Young Generation)、年老代(Tenured Generation)和永久代(Permanent Generation);在 .NET 中称之为第 0 代、第 1 代和第2代。
原理如下:
- 新对象放入第 0 代
- 当内存用量超过一个较小的阈值时,触发 0 代收集
- 第 0 代幸存的对象(未被收集)放入第 1 代
- 只有当内存用量超过一个较高的阈值时,才会触发 1 代收集
- 2 代同理
因为 0 代中的对象十分少,所以每次收集时遍历都会非常快(比 1 代收集快几个数量级)。只有内存消耗过于大的时候才会触发较慢的 1 代和 2 代收集。
因此,分代收集是目前比较好的垃圾回收方式。使用的语言(平台)有 jvm、.NET 。
golang的GC
root
首先标记root根对象,根对象的子对象也是存活的。
根对象包括:全局变量,各个G stack上的变量等。
标记
在之前的Go语言——内存管理一文中,分析过span是内存管理的最小单位,所以猜测gc的粒度也是span。

如图所示,通过gcmarkBits位图标记span的块是否被引用。对应内存分配中的bitmap区。
三色标记
- 灰色:对象已被标记,但这个对象包含的子对象未标记
- 黑色:对象已被标记,且这个对象包含的子对象也已标记,gcmarkBits对应的位为1(该对象不会在本次GC中被清理)
- 白色:对象未被标记,gcmarkBits对应的位为0(该对象将会在本次GC中被清理)
例如,当前内存中有A~F一共6个对象,根对象a,b本身为栈上分配的局部变量,根对象a、b分别引用了对象A、B, 而B对象又引用了对象D,则GC开始前各对象的状态如下图所示:
- 初始状态下所有对象都是白色的。
- 接着开始扫描根对象a、b; 由于根对象引用了对象A、B,那么A、B变为灰色对象,接下来就开始分析灰色对象,分析A时,A没有引用其他对象很快就转入黑色,B引用了D,则B转入黑色的同时还需要将D转为灰色,进行接下来的分析。
- 灰色对象只有D,由于D没有引用其他对象,所以D转入黑色。标记过程结束
- 最终,黑色的对象会被保留下来,白色对象会被回收掉。

STW
stop the world是gc的最大性能问题,对于gc而言,需要停止所有的内存变化,即停止所有的goroutine,等待gc结束之后才恢复。
go垃圾回收触发方式
- 阈值:默认内存扩大一倍,启动gc
- 定期:默认2min触发一次gc,src/runtime/proc.go:forcegcperiod
- 手动:runtime.gc()
GC流程
GO的GC是并行GC, 也就是GC的大部分处理和普通的go代码是同时运行的, 这让GO的GC流程比较复杂.
- Stack scan:Collect pointers from globals and goroutine stacks。收集根对象(全局变量,和G stack),开启写屏障。全局变量、开启写屏障需要STW,G stack只需要停止该G就好,时间比较少。
- Mark: Mark objects and follow pointers。标记所有根对象, 和根对象可以到达的所有对象不被回收。
- Mark Termination: Rescan globals/changed stack, finish mark。重新扫描全局变量,和上一轮改变的stack(写屏障),完成标记工作。这个过程需要STW。
- Sweep: 按标记结果清扫span
目前整个GC流程会进行两次STW(Stop The World), 第一次是Stack scan阶段, 第二次是Mark Termination阶段.
- 第一次STW会准备根对象的扫描, 启动写屏障(Write Barrier)和辅助GC(mutator assist).
- 第二次STW会重新扫描部分根对象, 禁用写屏障(Write Barrier)和辅助GC(mutator assist).
从1.8以后的golang将第一步的stop the world 也取消了,这又是一次优化; 1.9开始, 写屏障的实现使用了Hybrid Write Barrier, 大幅减少了第二次STW的时间.
写屏障
因为go支持并行GC, GC的扫描和go代码可以同时运行, 这样带来的问题是GC扫描的过程中go代码有可能改变了对象的依赖树。写屏障就是收集标记阶段对象依赖树修改记录的。
例如开始扫描时发现根对象A和B, B拥有C的指针。
- GC先扫描A,A放入黑色
- B把C的指针交给A
- GC再扫描B,B放入黑色
- C在白色,会回收;但是A其实引用了C。
为了避免这个问题, go在GC的标记阶段会启用写屏障(Write Barrier)。
启用了写屏障(Write Barrier)后,在GC第三轮rescan阶段,根据写屏障标记将C放入灰色,防止C丢失。
更多并发mark会导致的问题请看https://www.cnblogs.com/qqmomery/p/6661574.html?utm_source=tuicool&utm_medium=referral
golang的GC演变史
go 语言在 1.3 以前,使用的是比较蠢的传统 Mark-Sweep 算法。
1.3 版本进行了一下改进,把 Sweep 改为了并行操作。
1.5 版本进行了较大改进,使用了三色标记算法。go 1.5 在源码中的解释是“非分代的、非移动的、并发的、三色的标记清除垃圾收集器”。
从1.8以后的golang将第一步的stop the world 也取消了,这又是一次优化。
1.9开始, 写屏障的实现使用了Hybrid Write Barrier, 大幅减少了第二次STW的时间。
go 除了标准的三色收集以外,还有一个辅助回收功能,防止垃圾产生过快手机不过来的情况。这部分代码在 runtime.gcAssistAlloc 中。
但是 golang 并没有分代收集,所以对于巨量的小对象还是很苦手的,会导致整个 mark 过程十分长,在某些极端情况下,甚至会导致 GC 线程占据 50% 以上的 CPU。
因此,当程序由于高并发等原因造成大量小对象的gc问题时,最好可以使用 sync.Pool 等对象池技术,避免大量小对象加大 GC 压力。
参考并引用自以下资料:
https://blog.csdn.net/erlib/article/details/51850912(为Go语言GC正名-20秒到100微妙的演变史)
https://www.cnblogs.com/diegodu/p/9150840.html(golang垃圾回收机制)
https://blog.csdn.net/liangzhiyang/article/details/52669851(golang的goroutine调度机制)
https://blog.csdn.net/bairongdong1/article/details/52216360(跟雨痕看go源码-并发清除与三色标记)
https://www.cnblogs.com/qqmomery/p/6661574.html?utm_source=tuicool&utm_medium=referral(垃圾回收算法-三色标记法)
https://www.jianshu.com/p/8b0c0f7772da(Go语言垃圾回收GC)
golang垃圾回收的更多相关文章
- 【GoLang】golang垃圾回收 & 性能调优
golang垃圾回收 & 性能调优 参考资料: 如何监控 golang 程序的垃圾回收_Go语言_第七城市 golang的垃圾回收(GC)机制 - 两只羊的博客 - 博客频道 - CSDN.N ...
- Golang——垃圾回收GC(2)
1 垃圾回收中的重要概念 1.1 定义 In computer science, garbage collection (GC) is a form of automatic memory manag ...
- Golang垃圾回收机制(一)
原文: http://legendtkl.com/2017/04/28/golang-gc/ 1. Golang GC 发展 Golang 从第一个版本以来,GC 一直是大家诟病最多的.但是每一个版本 ...
- Golang垃圾回收机制(二)
原文:https://blog.csdn.net/qq_15427331/article/details/54613635 Go语言正在构建的垃圾收集器(GC),似乎并不像宣传中那样的,技术上迎来了巨 ...
- golang垃圾回收和SetFinalizer
golang自带内存回收机制--GC.GC通过独立的进程执行,它会搜索不再使用的变量,并释放.需要注意的是,进行GC会占用机器资源. GC是自动进行的.如果要手动进行GC,可以调用runtime.GC ...
- Golang——垃圾回收GC
Go 垃圾回收原理 Golang源码探索(三) GC的实现原理 引用计数:对每个对象维护一个引用计数,当引用该对象的对象被销毁时,引用计数减1,当引用计数器为0是回收该对象. 优点:对象可以很快的被回 ...
- golang 垃圾回收机制
用任何带 GC 的语言最后都要直面 GC 问题.在以前学习 C# 的时候就被迫读了一大堆 .NET Garbage Collection 的文档.最近也学习了一番 golang 的垃圾回收机制,在这里 ...
- golang 垃圾回收 gc
http://ruizeng.net/golang-gc-internals/ 摘要 在实际使用go语言的过程中,碰到了一些看似奇怪的内存占用现象,于是决定对go语言的垃圾回收模型进行一些研究.本文对 ...
- golang垃圾回收机制
golang的GC,1.8通过混合写⼊屏障, 使得STW降到了sub ms.go语言中程序代码执行和垃圾回收是并发执行的. 当前Go GC特征 :三色标记,并发标记和清扫,非分代,非紧缩,混合写屏障. ...
随机推荐
- 使用eclipse插件创建一个web project
使用eclipse插件创建一个web project 首先创建一个Maven的Project如下图 我们勾选上Create a simple project (不使用骨架) 这里的Packing 选择 ...
- 内置函数补充,__str__方法、__del__方法 和 __call__方法和元祖
一 .内置函数补充 1.isinstance函数: isinstance(obj,cls)检查obj是否是类 cls 的对象 使用该函数来判断一个函数的类型 2. issubclass(sub, su ...
- UVA 10303 How Many Trees? (catlan)
刚开始没看出时卡特兰数列.直接套高精度版 #include <map> #include <set> #include <list> #include <cm ...
- 内核中的多点触摸协议文档 Multi【转】
转自:http://www.arm9home.net/read.php?tid=24754 前段时间改写了一个GT801的内核驱动,仔细阅读 MT Event 上报的时候,发现这个驱动是针对 Andr ...
- Perl的Expect模块学习
Perl的Expect模块学习 http://www.xuebuyuan.com/1852717.html
- Smith-Waterman算法及其Java实现
Smith-Waterman算法是1981年Smith和Waterman提出的一种用来寻找并比较具有局部相似性区域的动态规划算法,很多后来的算法都是在该算法的基础上发展的.这是一种两序列局部比对算法, ...
- python算法:LinkedList(双向线性链表)的实现
LinkedList是一个双向线性链表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一 ...
- 《锋利的JQuery》读书要点笔记3——事件和动画
第四章 jQuery中的事件和动画 JS和HTML的交互是通过用户和浏览器操作页面时引发的事件来处理的,事件由浏览器自动生成. 4.1 jQuery中的事件 1. 加载DOM 这里主要是搞明白wind ...
- hdu 1115(多边形重心问题)
Lifting the Stone Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others ...
- 在windows下搭建爬虫框架,安装pywin32时出错?
出错原因:pip install pypiwin32(安装文件是pypiwin32而不是pywin32) pip intall pywin32