这是我参与「第五届青训营」伴学笔记创作活动的第8天
笨人纯小白,笔记包括一些上课学到的知识和课外总结的内容,如有错误请指正!
十一、Golang内存管理(三:垃圾回收GC)
11.1 垃圾回收算法
引用计数
对每一个对象维护一个计数器,引用该对象的对象被销毁时,计数器减一,计数器减为0时回收对象,表示没有被引用了。
- 优点:对象可以很快被回收,不会出现内存耗尽才回收。
- 缺点:对于
循环引用的处理并不友好。
标记清除
从根变量开始遍历所有引用的对象,引用对象标记为被引用,没有标记的对象被回收。
- 优点:解决了引用计数不能很好地处理循环引用的问题。
- 缺点:GC时需要暂停程序运行。
分代收集
按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,生命周期短的放入新生代,不同代有不同的回收算法。
- 优点:回收性能高。
- 缺点:算法复杂。
Go语言采用的是标记清除法,Go把它称作三色标记法。
11.2 根对象
根对象是在垃圾回收的过程中最先被检查的对象,包括:
- 全局变量:全局变量存在于程序整个生命周期。
- 协程栈中的对象,或者从栈上逃逸到堆上的对象。
- 被寄存器中的
指针引用的对象。
11.3 三色标记法
三色只是为了方便描述而抽象出来的一种说法,实际上并没有颜色,所说的三种颜色指的是对象的三种状态。
- 白色:对象未被标记,在mspan中的
gcmarkBits成员对应的位为0。 - 灰色:等待被标记的对象
- 黑色:对象被标记,在mspan中的
gcmarkBits成员对应的位为1。
三色标记的过程
假设根对象为A,内存中存在六个对象,分别是对象1~对象6,根对象A引用了对象2,而对象2引用了对象4。
第一步,初始所有对象都标记为白色。
第二步,从根对象A对其引用的对象进行扫描,扫描到的对象(对象2)标记为灰色。
第三步,将灰色对象(对象2)标记为黑色,把灰色对象(对象2)标记为黑色的同时,扫描其(对象2)引用的对象(对象4)标记为灰色。
第四步,将灰色对象(对象4)标记为黑色,同时扫描(对象4)其引用的对象,发现没有引用其他对象了,此时不存在灰色对象了。而对象1、对象3、对象5、对象6都是白色,对象2、对象4为黑色,当不存在灰色对象时表明标记过程结束,那么所有白色对象会被回收,黑色对象保留。
11.4 STW
STW全称为stop the world,意思是暂停程序的运行,在垃圾回收的过程中,如果不暂停程序的运行,指针传递会引起内存引用关系变化,如果错误的回收了还在使用的内存,带来的结果可能是灾难性的。例如下图中,A、B、C、D、E、F都被标记为黑色,而G、H为白色,那么G、H将会被回收,如果没有STW,此时程序继续运行,黑色对象突然又引用了G、H,而扫描过程已经结束了,那么就会错误的回收G、H这两个还在使用的对象。
因此在进行垃圾回收时,需要暂停程序的运行,专心做垃圾回收,等待垃圾回收结束后再恢复程序运行。
11.5 混合屏障
由于STW对程序的执行影响较大,对于一些应用是不可接受的,特别是WEB应用,所以Go也不断地在优化GC,提出了混合屏障,使得程序和GC同时运行。
删除屏障
灰色对象B引用了对象C,如下图:
GC扫描的过程,程序继续执行,此时程序移除了灰色对象B对白色对象C的引用,如下图:
程序继续执行,此时程序添加了黑色对象E对白色对象C的引用,如下图:
GC扫描的过程只会扫描根对象和灰色对象的引用,而不会扫描黑色对象的引用,所以上述情况会导致最终对象C被错误清除。
因此出现了删除屏障,删除屏障就是在GC扫描的过程中,对于引用被移除的对象(上图中的C对象),会立即置为灰色,保证其不会被错误清除。
所以上述过程变为了:
移除了B对象对C对象的引用:
删除屏障起作用,将被移除引用的C对象置灰:
程序添加了黑色对象E对白色对象C的引用:
最终保证了C对象不会被错误回收。
插入屏障
删除屏障保证了移除引用时,对象不会被错误清除,但是并不能保证所有情况下对象都不会被误回收。例如:
GC扫描的过程中,C对象不存在被谁引用,等待被删除:
GC扫描的过程中,程序继续执行,让黑色对象E新增了一个对C对象的引用:
所以C对象最后还是会被回收,于是就出现了插入屏障,插入屏障就是在GC扫描的过程中,对于新增引用的对象(上图中的C对象),会立即置为灰色,保证其不会被错误清除。
所以上述过程变为了:
新增引用,插入屏障起作用,对象C立即标记成灰色:
插入屏障最终保证了C对象不会被错误回收。
混合屏障
Go语言GC采用的是混合屏障来保证GC过程中对象不会被错误的回收,其实混合屏障就是两种写屏障——插入屏障和删除屏障一起使用。