Golang 1.7 GC的简单理解
2016-12-20
从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是为并发而生的,有些情况下,对象的分配速度远远高于后台并发标记的速度,堆内存会急剧增大,垃圾回收就有可能没有办法完成。这个时候就需要我们写出更好的代码了。