Go语言之内存垃圾回收

Memory Garbage Collection in Golang

Posted by alovn on July 5, 2021

Golang中每个协程的栈将在此协程退出时被整体回收,此栈上开辟的各个内存块没必要一个个单独回收。栈内存池并不由垃圾回收器回收。

对一个开辟在堆上的内存块,当它不再被使用,将在以后某个时刻被垃圾回收器进行回收。垃圾回收过程分为两个阶段:标记阶段和清扫阶段。

  • 标记阶段,垃圾回收器使用三色标记算法分析哪些内存块已经不再使用。
  • 清扫阶段,被标记为白色的内存块将被认为是不再使用的垃圾而回收掉。

目前官方Go标准运行时使用一个并发三色标记清扫算法来实现垃圾回收,下面是三色标记算法的大致过程,此垃圾回收算法不会移动内存块来整理内存碎片。

  • 在每一轮垃圾回收过程开始,所有的内存块被标记为白色。
  • 从程序对象根集合中开始遍历一层,找到的对象标记为灰色。
  • 然后将得到的灰色节点遍历一层,找到的对象标记为灰色,原灰色节点变为黑色。
  • 不断重复,直到不存在灰色对象,即直到只剩下白色和黑色对象。
  • 最终剩下的白色对象就是不可达的对象,也就是要清理的对象。

算法中使用三色而不使用两色的原因是在标记过程是并发的,用户的协程也在运行中。灰色标记可看作是一种临时状态。在标记过程中还增加了写屏障机制,简单来说就是:当一个已被标记为黑色的内存块被修改而引用一个白色标记内存块时,这个白色内存块需要被标记为灰色,否则的话白色内存块会被误当作垃圾而回收掉。

何时回收

垃圾回收器并不是时刻都在运行的,它只是每隔一段时间当某些条件达成后才会开始新的一轮垃圾回收过程。因此一个不再被使用的内存块不是立即被回收的,而是将在一段时间后被逐步回收的。

开始新一轮垃圾回收过程的默认条件是通过GOGC环境变量控制的。当一轮垃圾回收过程结束后,新申请的内存总和占上一轮垃圾回收时仍在被使用的所有内存总和的百分比超出此默认值时,将开始新一轮的垃圾回收过程。这个值决定了垃圾回收过程的频率(默认值为100)。可以通过runtime/debug.SetGCPercent函数在运行时刻动态修改,调用debug.SetGCPercent(-1)将会关闭自动垃圾回收。

调用runtime.GC函数可以手动开始一轮垃圾回收过程。

Golang(v1.16)同时还采用了另一个策略:一个Go程序的最大垃圾回收时间间隔为两分钟。

另外Golang中包变量开辟的内存块为常驻内存,永远不会被回收。

Golang运行时的实现比大多Java运行时要消耗少的多的内存,这也是我更喜欢Golang的一个原因。

关于Golang GC更详细的回收执行过程,我在之前已经整理在这里了:https://alovn.cn/docs/golang/memory/gc/