Notes on Golang Garbage Collection
/本文是《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
的过程也比较简单。在标记阶段会设置mspan
的gcmarkBits
域,清理工作就是用gcmarkBits
覆盖allocBits
域,并重新申请一个gcmarkBits
域。