本文基于 Go 1.13。关于内存管理的概念的讨论在我的文章 Go 中的内存管理和分配[1] 中有详细的解释。
Go GC 的作用是回收不再运用的内存。完成的算法是并发的三色标记和肃清回收法。本中文,我们研讨三色标记法,以及各个颜色的不同用途。
你可以在 Ken Fox 的 解读渣滓回收算法[2] 中了解更多关于不同渣滓回收机制的信息。
标记阶段
这个阶段阅读内存来了解哪些块儿是在被我们的代码运用和哪些块儿应该被回收。
但是,由于 GC 和我们的 Go 顺序并行,GC 扫描时期内存中某些对象的形状能够被改动,所以需求一个检测这种能够的变化的办法。为了处置这个潜在的成绩,完成了 写屏障[3] 算法,GC 可以追踪就任何的指针修正。使写屏障失效的独一条件是长久终止顺序,又名 “Stop the World”。
在进程启动时,Go 也在每个 processor 起了一个标记 worker 来辅佐标记内存。
然后,当 root 被参加四处置队列中后,标记阶段就末尾遍历和用颜色标记内存。
为了了解在标记阶段的每一步,我们来看一个复杂的顺序示例:
type struct1 struct {
a, b int64
c, d float64
e *struct2
}
type struct2 struct {
f, g int64
h, i float64
}
func main() {
s1 := allocStruct1()
s2 := allocStruct2()
func () {
_ = allocStruct2()
}()
runtime.GC()
fmt.Printf("s1 = %X, s2 = %X\n", &s1, &s2)
}
//go:noinline
func allocStruct1() *struct1 {
return &struct1{
e: allocStruct2(),
}
}
//go:noinline
func allocStruct2() *struct2 {
return &struct2{}
}
struct2 不包含指针,因此它被贮存在一个专门寄存不被其他对象援用的对象的 span 中。
不包含指针的结构体贮存在专有的 span 中
这增加了 GC 的任务,由于标记内存时不需求扫描这个 span。
分配任务完毕后,我们的顺序强迫 GC 重复前面的步骤。下面是流程图:
扫描内存
GC 从栈末尾,递归地顺着指针找指针指向的对象,遍历内存。扫描到被标记为 no scan 的 span 时,中止扫描。但是,这个任务是在多个协程中完成的,每个指针被参加到一个 work pool 中的队列。然后,后台运转的标记 worker 从这个 work pool 中拿到前面出列的 work,扫描这个对象然后把在这个对象里找到的指针参加到队列。
颜色标记
worker 需求一种记载哪些内存需求扫描的办法。GC 运用一种 三色标记算法[4],任务流程如下:
末尾时,一切对象都被以为是白色
root 对象(栈,堆,全局变量)被标记为灰色
这个初始步骤完成后,GC 会:
选择一个灰色的对象,标记为黑色
追踪这个对象的一切指针,把一切援用的对象标记为灰色
然后,GC 重复以上两步,直到没有对象可被标记。在这一时辰,对象非黑即白,没有灰色。白色的对象表示没有其他对象援用,可以被回收。
下面是前面例子的图示:
初始形状下,一切的对象被以为是白色的。然后,遍历到的且被其他对象援用的对象,被标记为灰色。假设一个对象在被标记为 no scan 的 span 中,由于它不需求被扫描,所以可以标记为黑色。
(责任编辑:admin)