golang gc 原理

GC介绍

GC是垃圾回收的缩写,是各种高级编程语言所必须的一种内存清理机制。Go中GC的runtime包定义。经历过三个阶段:1.3之前使用STW标记清扫、1.5版本使用三色标记法、1.8版本之后对三色标记进行优化,加上了写屏障机制。

GC的触发条件

定时触发,内存阈值触发,手动触发

工作清扫,STW

最早的GC在回收垃圾的过程中,是不允许向堆中写入数据的,GC回收时会暂停全部的work工作线程。等回收结束再通知线程继续工作。由此导致 SWT 成为了GC性能的一个瓶颈,在每次调用GC时都会暂时停止业务。

1.3版本的GC mark and sweep

工作流程:
1. 触发GC机制后,暂停全部的业务逻辑,找出不可达的对象和可达对象。
2. 开始标记所有的可达对象。
3. 标记完成后,删除所有不可达对象。
4. 停止暂停,继续运行程序。然后等待第二次触发,循环整个清理过程,直到程序的生命周期结束。

缺点:

stw会让程序出现暂停,删除堆区会造成大量的内存碎片。

标记清扫的优化思路:

正是因为 标记清扫需要让程序暂停,出现卡顿。所以提出了一个优化方案就是将标记完成后的清扫工作与程序一并运行。在标记完成后就停止暂停,减少暂停的时间范围。

GC 三色标记法原理

三色标记会将堆和栈中的数据维护成三种表,属于广度遍历内存空间。

  • 白色表(未遍历对象)
  • 黑色表(遍历过的对象)
  • 灰色表(临时对象)

三色标记工作流程:

1. 只要是创建在内存的对象,会默认标记成白色,将所有的对象放入白色表中。
2. 每次GC触发开始回收,会从root Roots根节点开始遍历所有对象,并把遍历到的可达对象标记成灰色。
3. 再根据灰色对象的可达对象继续遍历,将可达的对象标记灰色,原灰色对象标记成黑色。
4. 循环以上步骤直到所有可达对象都变成黑色。
5. 最终把白色的垃圾对象清理。

由三色标记产生的问题:(对象丢失问题)

因为三色标记没有采用 STW暂停保护机制,在GC进行回收的过程中还会不断的有数据加入内存,成为某个对象的下游节点。也会有原本存在的对象被删除或者被其它对象引用。

当满足:
一个灰色对象 丢失了它的下游白色对象,也就是对可达关系进行破坏。且这个白色对象又被其它的黑色对象引用时;因为黑色对象无法继续扫描遍历,就会出现白色对象丢失的情况。

为了解决这种对象丢失的问题,又不想通过STW降低性能,所以在 三色标记中加入了两个规则。
1. 强三色不变式
强三色规则不允许已经标记为黑色的对象来引用其它的白色对象。
2. 弱三色不变式
弱三色规则黑色对象可以引用其它的白色对象,但是该白色对象必须有一个它的上游灰色对象。必须满足黑白灰都存在。
三色标记法只需要满足强,弱两种方法之一,就可以保证对象不丢失。为了满足这两种规则,发明了屏障机制。

三色标记的屏障机制

为了实现强三式和弱三式规则推出了屏障机制。屏障机制又分为插入屏障和删除屏障。

5-1、插入屏障(实现强三色)

1. 当黑色对象添加下游对象时,下游对象会被标记为灰色。
2. 满足了强三色规则,不存在黑色对象引用白色对象,因为会强制转变成灰色。
3. 插入屏障只存在于堆区,栈区没有。
工作流程:
1. 当GC开始工作时。会触发堆区和栈区的扫描,在堆区开启插入屏障,栈不会开启插入屏障。
2. 此时根据 GC roots根节点进行正常的节点扫描,选出栈区和堆区的灰色对象。
3. 在第二次遍历时,堆区的黑色对象引用了一个白色对象,会将这个白色对象强制标记成灰色。如果是栈区的黑色对象,引用了白色对象,则栈区里面的白色不会标记为灰色。
4. 注意,当所有的灰色节点都被标记为黑色后。会重新遍历一次栈空间。
5. 此时会为栈区空间加上 STW停止一切线程,然后将所有的节点标记为白色,重新进行遍历,最终将栈区内所有的可达对象标记为黑,然后停机STW。
6. 最终清理堆区和栈区中所有的白色标记。
插入屏障的不足:

需要用到STW来重新扫描栈区,大约10~100ms的时间。
结束时会将栈区空间中的对象全部标记为白色,还是会造成程序暂停,虽然时间很短。

5-2、删除屏障:(实现弱三色)

当堆区和栈区中的对象被删除时,会被强制标记删除对象为灰色,然后保活一次遍历阶段。满足若三色不变式,保护灰色对象到白色对象的路径在第一轮遍历时不会断。

工作流程:
1. 当GC开始工作后,会在堆区和栈区中同样开启删除屏障。
2. 根据根节点进行遍历,遍历出灰色对象。
3. 当灰色对象的下游节点要删除时,如果该下游节点为灰色,会强制将其转换成灰色。同时会删除灰色对象和该对象的引用关系。
4. 因为删除屏障有保活机制,所以即便是它们之间没有了引用关系,但是都会变成灰色,然后在二次遍历后,所有的灰色标记都会变成黑色标记。
5. 当灰色对象被遍历完成后,还是会有一步保存STW快照的机制,会对当前内存空间的可达对象进行记录。然后重新遍历,将与根节点不可达的对象进行清理。
删除屏障的不足:

回收精度很低,一个对象即使被删除后,仍然可以活过第一轮的GC标记,在下一轮GC才会被清理。

1.8版本GC优化混合写屏障

1.8版本的GC结合了插入屏障和删除屏障的优点,变成了混合写屏障。但是栈空间依旧不会启动混和写屏障。

混合写屏障规则:

1. GC开始后第一时间将栈区所有可达对象标记为黑色。(这一步确保无需STW)
2. GC期间,任何在栈上创建的对象,均为黑色。
3. 不管是否在栈区和堆区,只要被删除的对象都会标记为灰色,并保活一次遍历。
4. 被添加的对象也会标记为灰色。
5. 不管是删除的对象,还是添加的对象,只要这个对象为可达对象,被根节点的其它线程所引用,最后一律标记为黑。

混合写屏障根据它的规则会出现4中场景。

1. 堆对象被堆对象删除,
2. 堆对象被堆对象引用,
3. 栈对象被栈对象删除,
4. 堆对象被栈对象引用

总之不管出现社么场景,只要符合混合写入的规则就不会被GC所清理。