GC内存管理机制

Go的GC目前使用的是无分代(对象没有代际之分)、不整理(回收过程中不对对象进行移动和整理)、并发(与用户代码并发执行)的三色标记清扫算法。原因:

tcmallocgoroutine

通常,垃圾回收器的执行过程被划分为两个半独立的组件:

  • 赋值器(Mutator):这一本质上是指用户态的代码。因为对垃圾回收器而言,用户态的代码仅仅只是在修改对象之前的引用关系,也就是在对象图(对象之间的引用关系的一个有向图)上进行操作。
  • 回收器(Collector):负责执行垃圾回收的代码
  • 内存分配器(Allocator):在堆上申请内存
  • 堆(Heap)
    GC

GC常见的方式

  1. 追踪式GC(Go、Java、JavaScript)
  2. 引用计数GC(Python、Objective-C)

”GC过程/三色标记法是什么?“

是一种描述追踪式GC的方法,它的作用是从逻辑上严密推导标记清理这种垃圾回收方法的正确性。

当垃圾回收开始时,只有白色对象。随着标记过程开始进行时,灰色对象开始出现(着色),这时候波面便开始扩大。当一个对象的所有子节点均完成扫描时,会被着色为黑色。当整个堆遍历完成时,只剩下黑色和白色对象,这时的黑色对象为可达对象,即存活;而白色对象为不可达对象,即死亡。这个过程可以视为以灰色对象为波面,将黑色对象和白色对象分离,使波面不断向前推进,直到所有可达的灰色对象都变为黑色对象为止的过程。如下图所示:

gc

”什么是写屏障、混合写屏障?“

Dijkstra 插入屏障、Yuasa 删除屏障两种写屏障的混合模式

需要理解三色标记清除算法中的强弱不变性以及赋值器的颜色,垃圾回收器的正确性体现在:不应出现对象的丢失,也不应错误的回收还不需要回收的对象。

可以证明,当以下两个条件同时满足时会破坏垃圾回收器的正确性:

  • 条件 1: 赋值器修改对象图,导致某一黑色对象引用白色对象;
  • 条件 2: 从灰色对象出发,到达白色对象的、未经访问过的路径被赋值器破坏。

Dijkstra 插入屏障:

灰色赋值器的 Dijkstra 插入屏障的基本思想是避免满足条件 1

// 灰色赋值器 Dijkstra 插入屏障
func DijkstraWritePointer(slot *unsafe.Pointer, ptr unsafe.Pointer) {
    shade(ptr)
    *slot = ptr
}

gc

Dijkstra 插入屏障的好处在于可以立刻开始并发标记。但存在两个缺点:

  1. 由于 Dijkstra 插入屏障的“保守”,在一次回收过程中可能会残留一部分对象没有回收成功,只有在下一个回收过程中才会被回收;
  2. 在标记阶段中,每次进行指针赋值操作时,都需要引入写屏障,这无疑会增加大量性能开销;为了避免造成性能问题,Go 团队在最终实现时,没有为所有栈上的指针写操作,启用写屏障,而是当发生栈上的写操作时,将栈标记为灰色,但此举产生了灰色赋值器,将会需要标记终止阶段 STW 时对这些栈进行重新扫描。

Yuasa 删除屏障:

黑色赋值器的 Yuasa 删除屏障其基本思想是避免满足条件 2

// 黑色赋值器 Yuasa 屏障
func YuasaWritePointer(slot *unsafe.Pointer, ptr unsafe.Pointer) {
    shade(*slot)
    *slot = ptr
}
*slotptr*slotshade(*slot)*slot

Yuasa 删除屏障的优势则在于不需要标记结束阶段的重新扫描,结束时候能够准确的回收所有需要回收的白色对象。缺陷是 Yuasa 删除屏障会拦截写操作,进而导致波面的退后,产生“冗余”的扫描:
在这里插入图片描述

Go 在 1.8 的时候为了简化 GC 的流程,同时减少标记终止阶段的重扫成本,将 Dijkstra 插入屏障和 Yuasa 删除屏障进行混合,形成混合写屏障。该屏障提出时的基本思想是:对正在被覆盖的对象进行着色,且如果当前栈未扫描完成,则同样对指针进行着色。

// 混合写屏障
func HybridWritePointerSimple(slot *unsafe.Pointer, ptr unsafe.Pointer) {
    shade(*slot)
    shade(ptr)
    *slot = ptr
}
ptr

“STW 是什么意思”

Stop The World,这一动作发生的这一段时间间隔,万物静止,停止赋值器进一步操作对象图的一段过程。

当前版本的 Go 以 STW 为界限,可以将 GC 划分为五个阶段:

阶段说明赋值器状态
SweepTermination清扫终止阶段,为下一个阶段的并发标记做准备工作,启动写屏障STW
Mark扫描标记阶段,与赋值器并发执行,写屏障开启并发
MarkTermination标记终止阶段,保证一个周期内标记任务完成,停止写屏障STW
GCoff内存清扫阶段,将需要回收的内存归还到堆中,写屏障关闭并发
GCoff内存归还阶段,将过多的内存归还给操作系统,写屏障关闭并发