这是我参与「第五届青训营 」伴学笔记创作活动的第 4天
1 GC介绍
GC(英语:Garbage Collection,缩写为GC),它能避免手动内存管理,让程序员专注于实现业务逻辑,保证内存使用的正确性和安全性。
2 GC解决的手动内存管理易发问题
2.1 double-free problem
双重释放,程序对同一个指针指向的内存重复释放free()了两次。
2.2 use-after-free problem
某块内存在释放后还能被用户使用;一般存在于,free后没有将指针置为NULL,导致野指针。
3.Go采用的GC算法
当前Go使用的垃圾回收机制是三色标记法配合写屏障和辅助GC,三色标记法是标记-清除法的一种增强版本。
3.1 三色标记算法:
首先由三个概念:
白色对象,最终被回收的对象
灰色对象,活跃的对象,但后期会寻找该类对象是否有引用其他对象
黑色对象,活跃的对象,不会被回收的对象
算法的执行流程是:
1、所有的对象在最开始初始化的时候都是白色对象
2、首先从根对象开始,扫描所有根对象,然后标记该对象为灰色对象。这里的根对象是什么对象呢?因为我们有一个后台的垃圾回收线程处在运行状态,当前的根对象指的是当前的全部全局变量以及所有Goroutine中的栈中的对象。
3、从灰色对象队列中取出一个对象,看这个对象是否有引用其他的对象,如果没有引用其他的对象,则将该对象标记为黑色对象,放入黑色对象队列。如果引用了其他的对象,则被引用的对象被标记为灰色,同时该引用对象标记为黑色,并放入黑色对象队列。总的来说也就是取出来的灰色对象不管有没有引用其他的对象,都会被标记为黑色对象,并放入到黑色对象队列,只不过如果有引用其他对象,就把其他对象标记为灰色,然后放入到灰色的队列而已。
4、如果灰色队列不空,则继续步骤3,最终只剩下黑色对象和白色对象,白色对象就是被清理的对象。
好,到了这里三色标记算法已经明白了,那我们看一下Go的GC具体是如何执行的?
1、Mark
首先是标记(mark)对象,但是Go将标记过程进行了一个划分,这样就能尽可能的减少STW的时间,也就能进一步提升GC的性能。GO的标记阶段分为:Mark Perpare和GC Drains两部分。
Mark Prepare:
初始化GC任务,同时开启写屏障(write barrier)和辅助GC(mutator assist),统计根对象的个数,这个过程是需要STW的。
GC Drains:
扫描所有root对象,也就是扫描全局对象和Gorotine上的栈对象(扫描对应Gorotinue的栈时需停止该Gorotinue),将其加入灰色队列,并循环处理灰色队列的对象,直到灰色队列为空。该过程后台并行执行
好,以上是标记阶段,下面是标记阶段结束之后的操作。
2、Mark Termination
完成标记工作,重新扫描(re-scan)全局指针和栈。因为Mark和用户程序是并行的,所以在Mark过程中可能会有新的对象分配和指针赋值,这个时候就需要通过写屏障(write barrier)记录下来,re-scan 再检查一下。这个过程也是会STW的。
3、Sweep
按照标记结果回收所有的白色对象,该过程后台并行执行。
3.2 写屏障
在每一轮GC开始时会初始化一个叫做“屏障”的东西,然后由它记录第一次scan时各个对象的状态,以便和第二次re-scan进行比对,引用状态变化的对象被标记为灰色以防止丢失,将屏障前后状态未变化对象继续处理。
3.3 辅助GC
从上面的GC工作的完整流程可以看出Golang GC实际上把单次暂停时间分散掉了,本来程序执⾏可能是“⽤户代码-->⼤段GC-->⽤户代码”,那么分散以后实际上变成了“⽤户代码-->⼩段 GC-->⽤户代码-->⼩段GC-->⽤户代码”这样。如果GC回收的速度跟不上用户代码分配对象的速度呢? Go 语⾔如果发现扫描后回收的速度跟不上分配的速度它依然会把⽤户逻辑暂停,⽤户逻辑暂停了以后也就意味着不会有新的对象出现,同时会把⽤户线程抢过来加⼊到垃圾回收⾥⾯加快垃圾回收的速度。这样⼀来原来的并发还是变成了STW,还是得把⽤户线程暂停掉,要不然扫描和回收没完没了了停不下来,因为新分配对象⽐回收快,所以这种东⻄叫做辅助回收。
今日青训营课程中学习的内存管理方法如分代GC,引用计数法,标记清除法,标记压缩清除法,复制算法等都在go采用的算法中有所体现,算法没有最优越的,只有最适合的。