本文希望分享一个轻量版增量垃圾回收器实现:TGC

目标:

  1. 不能stop the world
  2. 选择使用,无需全局替换
  3. 使用方便,无需繁琐的手动注册
  4. 尽量轻量实现,方便集成和维护
  5. 尽量贴合C++自身特点实现

目标用户

如Blink GC的需求一样,用shared_ptr已经无法方便解决生命周期问题的情况。但是Blink GC太复杂(不然也不会有本文), Boehm GC有需要操作系统特殊支持等使用限制。

实现思路分析:

  1. 使用传统的标记清除演算法的话,要不停顿,可以增量或者并行。
  2. C++不好处理内存整理的操作,因为C++内存内的数据直接拷贝会有很多限制。
  3. 并行回收会较大增加实现复杂度。
  4. python的引用计数加扫描标记可以很快回收临时垃圾,C++有RAII
  5. 可用3色标记增量演算法,实现简单,但吞吐量小
  6. 智能指方式针兼容性较好,可以和三方库、遗留代码并存
  7. 可以与已有内存池对接更好
  8. 不优先考虑:
    1. 性能,性能敏感和GC是矛盾的,要处理好太复杂
    2. 吞吐量,C++不像Java等语言一样必须重度依赖GC

其实很多相关技术都很成熟了,所以这里我无需重复,只想分享一下C++特定的问题:

最开始我是实现的引用计数加标记回收的方式,好处是大部分垃圾都可以用引用计数处理,循环引用才由扫描标记回收,但是实现中发现逻辑很复杂很不稳定。这有悖于轻量的原则,所以放弃了这种方式,全部由标记回收方式统一处理。

首先,GC中保存所有的指针用于扫描,也保存了每个对象对应的meta信息用于保存:回收状态、类信息。类信息包括:成员指针偏移,构造析构器,子指针枚举器。因为C++本身的限制,这些信息在编译时间很难方便的获取到(也不是不可以,但是大大增加了复杂度),所以TGC综合权衡后,采用牺牲部分性能的动态方式。

怎么发现根

  1. 默认构造的指针都是根,比如全局变数
  2. 指针注册到GC中时查找owner对象(即包容这个指针成员的class实例)找不到即为根

怎么跟踪引用关系:

  1. Class对象中成员指针,指针构造时查找owner并注册到owner的meta中。
  2. 容器中保存指针元素,对容器,特化子指针枚举器,然后在标记阶段通过枚举器标记所有子指针为叶子。
  3. 所有可能保存指针的复合对象都需要为可跟踪的,除了标准容器,还有function对象
  4. 函数栈上的指针怎么处理?无需处理由于收集函数是手动在事件循环外触发,此时已经退出大部分函数,RAII已经清除了栈上的指针。

容器这里有个难点,在构造指针的时候无法很简单的判断出自己是否在容器中,因此推迟到枚举器里面标记,因为在枚举器中一定就是叶子。

这里很多细节没有具体讨论,可以查看TGC的实现。

限制:

  1. 如shared ptr一样必须用指定方式构造对象
  2. 可以不使用gc容器,因为RAII可以保证指针销毁时从gc自动清除。但是脱离gc容器可能引起循环引用无法释放导致RAII无法执行析构,从而无法从gc中清除引用造成泄露。
  3. 从gc指针取出的原始指针,将脱离gc的管理。
  4. 因为三色标记的原因,扫描阶段的指针变化需要特殊处理(write barrier),所以gc指针的复制操作有一点点消耗(tgc中的onPtrChanged实现)
  5. 为了简化实现,未考虑异常和多线程。

由于只有小规模使用,因此TGC未必达到生产质量,请有兴趣的朋友提issue。笔者能力有限,如有错误请不吝赐教。


推荐阅读:
相关文章