Golang与区块链go语言go

Go语言——垃圾回收GC

2018-10-25  本文已影响2912人  陈先生_9e91

Go语言——垃圾回收GC

参考:

Go 垃圾回收原理

Golang源码探索(三) GC的实现原理

Getting to Go: The Journey of Go's Garbage Collector

Go的三色标记GC

root

首先标记root根对象,根对象的子对象也是存活的。

根对象包括:全局变量,各个G stack上的变量等。

标记

在之前的Go语言——内存管理一文中,分析过span是内存管理的最小单位,所以猜测gc的粒度也是span。

type mspan struct {
    // allocBits and gcmarkBits hold pointers to a span's mark and
    // allocation bits. The pointers are 8 byte aligned.
    // There are three arenas where this data is held.
    // free: Dirty arenas that are no longer accessed
    //       and can be reused.
    // next: Holds information to be used in the next GC cycle.
    // current: Information being used during this GC cycle.
    // previous: Information being used during the last GC cycle.
    // A new GC cycle starts with the call to finishsweep_m.
    // finishsweep_m moves the previous arena to the free arena,
    // the current arena to the previous arena, and
    // the next arena to the current arena.
    // The next arena is populated as the spans request
    // memory to hold gcmarkBits for the next GC cycle as well
    // as allocBits for newly allocated spans.
    //
    // The pointer arithmetic is done "by hand" instead of using
    // arrays to avoid bounds checks along critical performance
    // paths.
    // The sweep will free the old allocBits and set allocBits to the
    // gcmarkBits. The gcmarkBits are replaced with a fresh zeroed
    // out memory.
    allocBits  *gcBits
    gcmarkBits *gcBits
}
bitmap

如图所示,通过gcmarkBits位图标记span的块是否被引用。对应内存分配中的bitmap区。

三色标记

例如,当前内存中有A~F一共6个对象,根对象a,b本身为栈上分配的局部变量,根对象a、b分别引用了对象A、B, 而B对象又引用了对象D,则GC开始前各对象的状态如下图所示:

  1. 初始状态下所有对象都是白色的。
  2. 接着开始扫描根对象a、b; 由于根对象引用了对象A、B,那么A、B变为灰色对象,接下来就开始分析灰色对象,分析A时,A没有引用其他对象很快就转入黑色,B引用了D,则B转入黑色的同时还需要将D转为灰色,进行接下来的分析。
  3. 灰色对象只有D,由于D没有引用其他对象,所以D转入黑色。标记过程结束
  4. 最终,黑色的对象会被保留下来,白色对象会被回收掉。
4

STW

stop the world是gc的最大性能问题,对于gc而言,需要停止所有的内存变化,即停止所有的goroutine,等待gc结束之后才恢复。

触发

go gc

go gc

GO的GC是并行GC, 也就是GC的大部分处理和普通的go代码是同时运行的, 这让GO的GC流程比较复杂.

  1. Stack scan:Collect pointers from globals and goroutine stacks。收集根对象(全局变量,和G stack),开启写屏障。全局变量、开启写屏障需要STW,G stack只需要停止该G就好,时间比较少。
  2. Mark: Mark objects and follow pointers。标记所有根对象, 和根对象可以到达的所有对象不被回收。
  3. Mark Termination: Rescan globals/changed stack, finish mark。重新扫描全局变量,和上一轮改变的stack(写屏障),完成标记工作。这个过程需要STW。
  4. Sweep: 按标记结果清扫span

目前整个GC流程会进行两次STW(Stop The World), 第一次是Stack scan阶段, 第二次是Mark Termination阶段.

从1.8以后的golang将第一步的stop the world 也取消了,这又是一次优化; 1.9开始, 写屏障的实现使用了Hybrid Write Barrier, 大幅减少了第二次STW的时间.

写屏障

因为go支持并行GC, GC的扫描和go代码可以同时运行, 这样带来的问题是GC扫描的过程中go代码有可能改变了对象的依赖树。

例如开始扫描时发现根对象A和B, B拥有C的指针。

  1. GC先扫描A,A放入黑色
  2. B把C的指针交给A
  3. GC再扫描B,B放入黑色
  4. C在白色,会回收;但是A其实引用了C。

为了避免这个问题, go在GC的标记阶段会启用写屏障(Write Barrier).

启用了写屏障(Write Barrier)后,在GC第三轮rescan阶段,根据写屏障标记将C放入灰色,防止C丢失。

上一篇下一篇

猜你喜欢

热点阅读