在 Go 中,使用的垃圾回收(GC)策略是 Concurrent Mark-Sweep(CMS)。这是一种基于分代的、并发的、并行的垃圾回收器,设计上考虑到了低延迟、高吞吐量和尽可能小的暂停时间。
Go 的 GC 由两个主要阶段组成:标记(Mark)阶段和清扫(Sweep)阶段。
- 标记阶段(Mark Phase):这个阶段的目的是找到堆中所有还活跃的对象。这个过程分为三个子阶段:开始标记(Mark Setup)、并发标记(Concurrent Mark)和标记终止(Mark Termination)。
- 开始标记(Mark Setup):这是一个非常短的“Stop The World”(STW)阶段,所有的 goroutines 都会暂停执行。在这个阶段,GC 会设置一些必要的参数,然后启动后台的并发标记 goroutine。
- 并发标记(Concurrent Mark):在这个阶段,后台的 goroutine 会并发地对堆中的对象进行标记,同时前台的 goroutine 也会继续执行,修改堆上的数据。并发标记阶段会追踪和标记所有从根对象(例如全局变量和当前活动的 goroutine 栈)直接或间接引用的对象。
- 标记终止(Mark Termination):这是第二个“Stop The World”(STW)阶段。在这个阶段,所有的 goroutines 都会再次暂停执行。GC 会完成最后的标记工作,处理并发标记阶段中遗漏的对象,并为下一阶段的清扫做好准备。
- 清扫阶段(Sweep Phase):在这个阶段,GC 会清理掉那些在标记阶段中被标记为“非活动”的对象,回收这些对象占用的内存。清扫阶段是并发执行的,也就是说,GC 会在后台清理对象,而前台的 goroutine 会继续执行,可能创建新的对象。
在 Go 中,垃圾回收器还使用了一种叫做“分代回收”(Generational GC)的优化技术。简单来说,这种技术的思想是,新创建的对象更有可能在短时间内变得非活动,因此应该更频繁地对它们进行回收。在每次 GC 后,Go 会标记出那些有对象被分配但是没有被清理的内存区域,然后在下一次 GC 时优先对这些区域进行标记和清扫。
触发GC
Go的垃圾回收并不是定时触发的,它的触发时机主要取决于内存的分配情况。Go使用的是一个基于分配的触发机制,而不是基于时间的触发机制。
当你的程序分配的内存超过一定阈值时,垃圾回收器就会触发。这个阈值是由上一次垃圾回收之后的存活对象数量计算出来的。具体来说,如果上次垃圾回收结束后,存活对象占用的内存大小为X,那么当新分配的内存加上X达到2X时,就会触发新的垃圾回收。
换句话说,垃圾回收的触发是基于堆的增长速率的。如果你的程序持续分配新的内存但不释放旧的内存,垃圾回收会更频繁地触发。反之,如果你的程序的内存分配速率较低,或者分配的内存很快就被释放,垃圾回收就不会那么频繁地触发。
需要注意的是,虽然这是Go的默认行为,但是你可以通过调用runtime.GC()
函数来手动触发垃圾回收。此外,你还可以通过调整GOGC
环境变量来改变垃圾回收的触发频率,例如,设置GOGC=200
(这是默认值)表示当堆的大小增长到上次垃圾回收后的两倍时触发垃圾回收,设置GOGC=100
则表示当堆的大小增长到上次垃圾回收后的一倍时触发垃圾回收。