本文是《Go语言设计与实现》7.2节垃圾收集器的学习笔记,同时参考了@小徐先生 的知乎回答.

1. Golang 垃圾回收原理 总结

1.1 触发垃圾回收

三种情况可以触发垃圾回收:

  • 申请内存: 堆内存的分配达到控制器计算的触发堆的大小
  • 后台触发: 后台开启
  • 手动触发: 主动调用runtime.GC来进行垃圾回收

1.2 启动垃圾回收

无论1.1中的哪种情况,最终都会调用runtime.gcStart来启动垃圾回收过程,此时进入标记准备阶段

该函数首先会调用runtime.stopTheWorldWithSema将所有协程挂起,根据CPU数量在后台启动gcBgMarkWorker,并在做完其他的一些准备工作之后(如开启写屏障等),调用runtime.startTheWorldWithSema恢复程序。

1.3 并发标记与标记辅助

在1.2中启动的gcBgMarkWorker 协程并不会直接开始运行,而是在完成准备工作之后挂起,等待垃圾收集控制器或者调度器的唤醒,唤醒之后进入并发标记阶段

每一个P都对应着一个runtime.gcWork对象,它是垃圾收集工作池的抽象。在并发标记阶段开始时,gcBgMarkWorker首先扫描根对象,标记为灰色将其加入到工作池中;此后不断从工作池中取出灰色对象,将其标记为黑色,并将该对象指向的所有对象都加入到工作池中,如此重复直到工作池为空。

为了保证用户程序分配内存的速度不会超出后台任务的标记速度,golang runtime还引入了标记辅助技术,每个goroutine要求分配多少内存就需要完成多少标记任务,如果不能满足要求,那么该goroutine就会被挂起。

1.4 标记终止

标记终止阶段完成了一些工作,比如关闭写屏障,将垃圾回收器的状态设置为_Goff,统计与垃圾回收相关的数据等等,此处不再详细介绍。

1.5 内存清理

内存清理阶段过程中,函数runtime.gcSweep会唤醒内存清理协程,该协程调用runtime.sweepone来进行内存清理,每轮完成对一个mspan的清理工作,在此之后调用runtime.Gosched主动让渡P的执行权,通过懒清理的方式逐步推进清理流程。

清理mspan的过程也比较简单。在标记阶段会设置mspangcmarkBits域,清理工作就是用gcmarkBits覆盖allocBits域,并重新申请一个gcmarkBits域。