三色标记法是传统 Mark-Sweep 的一个改进,它是一个并发的 GC 算法。
原理如下,
- 首先创建三个集合:白、灰、黑。
- 将所有对象放入白色集合中。
- 然后从根节点开始遍历所有对象(注意这里并不递归遍历),把遍历到的对象从白色集合放入灰色集合。
- 之后遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合
- 重复 4 直到灰色中无任何对象
- 通过write-barrier检测对象有变化,重复以上操作
- 收集所有白色对象(垃圾)
这个算法可以实现 “on-the-fly”,也就是在程序执行的同时进行收集,并不需要暂停整个程序。
但是也会有一个缺陷,可能程序中的垃圾产生的速度会大于垃圾收集的速度,这样会导致程序中的垃圾越来越多无法被收集掉。
go语言垃圾回收总体采用的是经典的mark and sweep算法的改进——三色标记法
ps: 可能出现暂时性内存无法释放问题的解释
-
go的垃圾回收有个触发阈值,这个阈值会随着每次内存使用变大而逐渐增大(如初始阈值是10MB则下一次就是 20MB,再下一次就成为了40MB...),如果长时间没有触发gc,go会主动触发一次(2min)。高峰时内存使用量上 去后,除非持续申请内存,靠阈值触发gc已经基本不可能,而是要等最多2min主动gc开始才能触发gc。
-
go语言在向系统交还内存时只是告诉系统这些内存不需要使用了,可以回收;同时操作系统会采取“拖延症”策略, 并不是立即回收,而是等到系统内存紧张时才会开始回收这样该程序又重新申请内存时就可以获得极快的分配速度。
表面上,指针参数的性能要更好一些,但是实际上具体分析,被复制的指针会延长目标对象的生命周期,还可能会 导致他被分配到堆上去,那么其性能消耗就得加上堆内存分配和垃圾回收的成本。
解决办法:
- 减少对象数量,gc需要停止goroutines,
- 期待新版本(最新版本的go已经把gc调优到0.1 ms以下, 实时性非常高的需要谨慎使用)。