从Go 1.7 runtime包理解Golang GC

Go也是垃圾回收的,实现方式和别的语言不太一样。

先从Go的标准库的runtime包说起,这个包有很多运行时相关的类型和函数。

调用runtimea.GC()可以触发GC,但我们一般不会这么做,先读一下这个函数的注释说明。

1GC runs a garbage collection and blocks the caller until the garbage collection is complete. 
2It may also block the entire program

大概的意思是,Go的GC触发时会阻塞整个程序的运行。这个在垃圾回收里面有一个比较有名的名词叫STW,Stop the World。就是说程序在GC时“整个世界会停止下来”。 Go垃圾回收的STW一直是Go语言被指责和诟病最多的地方,也是Go的每个版本都努力改进的地方。

runtime包mgc.gohttps://golang.org/src/runtime/mgc.go源码的注释里大致描述了Go的当前版本的垃圾回收的过程。

1// Garbage collector (GC).
2     6	//
3     7	// The GC runs concurrently with mutator threads, is type accurate (aka precise), allows multiple
4     8	// GC thread to run in parallel. It is a concurrent mark and sweep that uses a write barrier. It is
5     9	// non-generational and non-compacting. Allocation is done using size segregated per P allocation
6    10	// areas to minimize fragmentation while eliminating locks in the common case.

上面的注释指出,Go的GC具有四个特点:

  • 并发标记和清理(concurrent mark and sweep)
  • 写屏障(write barrier)
  • 非分代(non-generational)
  • 非紧缩(non-compacting)

Go在垃圾回收时采用并发标记和清除,注意标记前面的并发两个字,这个在Go 1.5之前是没有的。 Go 1.5之前的标记是在STW的状态下完成的。

先简单理解一下标记,就是标记哪些对象可用,哪些对象不可用。 Go 1.5开始做到并发标记其实是很困难的,因为在并发标记的同时,程序的代码还在运行,有可能已经扫描过的内存区域又发生了修改,同时在此过程中也会分配新的对象。 启动垃圾回收的时机也是一个难题,因为如果频繁垃圾回收会浪费CPU,影响应用代码的性能,而如果垃圾回收启动太晚,会导致堆内存累计太多,如何平衡这个问题是Go的每个版本更新时都要面对的。

Go的GC采用的是三色标记法:

1.初始所有对象都是白色的 1.扫描所有可以到达的对象,将其标记成灰色,放入待处理队列 1.从队列中提取灰色对象,将这些灰色对象引用的对象标记成新的灰色对象,原灰色对象自身被标记成黑色 1.内存写屏障监视内存修改时,重新标记或者放回队列

以上当完成全部的扫描和标记后,内存中就就只剩两种颜色的对象,即白色和黑色。黑色表示活跃的对象,清理的操作只要将白色的对象回收即可。

还要理解一种情况,Go是为并发而生的,有些情况下,对象的分配速度远远高于后台并发标记的速度,堆内存会急剧增大,垃圾回收就有可能没有办法完成。这个时候就需要我们写出更好的代码了。

参考