本文希望分享一个轻量版增量垃圾回收器实现:TGC
目标:
- 不能stop the world
- 选择使用,无需全局替换
- 使用方便,无需繁琐的手动注册
- 尽量轻量实现,方便集成和维护
- 尽量贴合C++自身特点实现
目标用户:
如Blink GC Oilpan的需求一样,用shared_ptr已经无法方便解决生命周期问题的情况。但是Blink GC太复杂(不然也不会有本文), Boehm GC有需要操作系统特殊支持等使用限制。
实现思路分析:
- 使用传统的标记清除算法的话,要不停顿,可以增量或者并行。
- C++不好处理内存整理的操作,因为C++内存内的数据直接拷贝会有很多限制。
- 并行回收会较大增加实现复杂度。
- python的引用计数加扫描标记可以很快回收临时垃圾,C++有RAII
- 可用3色标记增量算法,实现简单,但吞吐量小
- 智能指方式针兼容性较好,可以和三方库、遗留代码并存
- 可以与已有内存池对接更好
- 不优先考虑:
- 性能,性能敏感和GC是矛盾的,要处理好太复杂
- 吞吐量,C++不像Java等语言一样必须重度依赖GC
其实很多相关技术都很成熟了,所以这里我无需重复,只想分享一下C++特定的问题:
最开始我是实现的引用计数加标记回收的方式,好处是大部分垃圾都可以用引用计数处理,循环引用才由扫描标记回收,但是实现中发现逻辑很复杂很不稳定。这有悖于轻量的原则,所以放弃了这种方式,全部由标记回收方式统一处理。
首先,GC中保存所有的指针用于扫描,也保存了每个对象对应的meta信息用于保存:回收状态、类信息。类信息包括:成员指针偏移,构造析构器,子指针枚举器。因为C++本身的限制,这些信息在编译时间很难方便的获取到(也不是不可以,但是大大增加了复杂度),所以TGC综合权衡后,采用牺牲部分性能的动态方式。
怎么发现根
- 默认构造的指针都是根,比如全局变量
- 指针注册到GC中时查找owner对象(即包容这个指针成员的class实例)找不到即为根
怎么跟踪引用关系:
- Class对象中成员指针,指针构造时查找owner并注册到owner的meta中。
- 容器中保存指针元素,对容器,特化子指针枚举器,然后在标记阶段通过枚举器标记所有子指针为叶子。
- 所有可能保存指针的复合对象都需要为可跟踪的,除了标准容器,还有function对象
- 函数栈上的指针怎么处理?无需处理由于收集函数是手动在事件循环外触发,此时已经退出大部分函数,RAII已经清除了栈上的指针。
容器这里有个难点,在构造指针的时候无法很简单的判断出自己是否在容器中,因此推迟到枚举器里面标记,因为在枚举器中一定就是叶子。
这里很多细节没有具体讨论,可以查看TGC的实现。
限制:
- 如shared ptr一样必须用指定方式构造对象
- 可以不使用gc容器,因为RAII可以保证指针销毁时从gc自动清除。但是脱离gc容器可能引起循环引用无法释放导致RAII无法执行析构,从而无法从gc中清除引用造成泄露。
- 从gc指针取出的原始指针,将脱离gc的管理。
- 因为三色标记的原因,扫描阶段的指针变化需要特殊处理(write barrier),所以gc指针的复制操作有一点点消耗(tgc中的onPtrChanged实现)
- 为了简化实现,未考虑异常和多线程。
由于只有小规模使用,因此TGC未必达到生产质量,请有兴趣的朋友提issue。笔者能力有限,如有错误请不吝赐教。