GC算法笔记(五)概括与比较

前面几篇笔记里我们分别介绍了四种基本的 GC 算法:

本文将它们放在一起,做一个总结概括~


追踪式 VS. 引用计数式

类型 算法 特征
追踪式 GC 标记-清除算法
复制算法
标记-整理算法
要先通过堆遍历找出所有的活动对象,然后在反向确定出未遍历到的垃圾
引用计数式 GC 引用计数法 通过引用关系的创建或删除直接判定对象的存活性

移动式 VS. 非移动式

类型 算法 特征
移动式 复制算法
标记-整理算法
一定要找到并且更新指向被移动对象的全部引用
非移动式 标记-清除
引用计数法
只要找到某个活动对象的一个引用就足够了

四类基本算法的优缺点比较

算法 优点 局限性 其他
标记-清除算法 不会给赋值器带来任何额外的读写开销;
具有较高的吞吐量;
具有较高的空间利用率(相比于复制算法和引用计数法);
不用移动对象,可与保守式回收器兼容;
长期运行的程序堆可能会逐渐碎片化;
分配速度受限,因为分块不连续;
一个通用方案:尽量长久地使用标记-清除算法进行水后,仅当碎片化到达一定程度时才使用标记-整理回收。
标记-整理算法 允许极为快速的顺序分配;
解决了碎片化问题;
任意顺序算法只能处理单一大小的对象;
整理过程需要两次到三次整堆遍历,速度较慢,吞吐量较差;
对象头部可能需要额外的空间用于保存迁移信息;
回收器遍历和分配对象的顺序会影响赋值器的局部性,对高速缓存命中率造成影响,从而影响程序性能;
复制算法 消除碎片;
可实现高速分配;
较高的吞吐量;
不用引入额外的空间存放迁移信息;
堆的利用率低(二分之一),导致回收次数比其他算法更多; 可用空间减少,回收次数增多对性能的影响取决于赋值器与分配器之间的平衡、应用程序的特征和可用堆空间的大小等
引用计数法 可即刻回收垃圾(将内存管理开销分摊在程序运行过程中,垃圾被立即回收,因此可以持续操作即将填满的堆,而不必像追踪式GC需要一定的保留空间);
具有较好的局部性;
不用沿指针查找;
可以在代码中实现,无需作为语言的运行时环境的一部分,开发者可以完全控制引用计数的使用(自主处理性能开销和安全性之间的平衡);
计数器更新频繁,给赋值器带来额外开销;
开发者必须避免在更新计数器值的过程中可能出现的竞争问题;
计数器值需要占用额外空间;
无法处理循环引用;
即使系统部分不可用,引用计数法也能回收部分内存,这一特性对分布式系统十分有用;
引用计数法可以管理数量较少但所有者关系复杂的资源,这些资源通常较大,因此计数器值占用的额外开销可以忽略;

四类基本算法的部分实现与优化

标记-清除算法

算法 优点 局限性
位图标记 位图使标记位更加密集;
标记过程不会修改任何对象,清除过程不会对任何活动对象进行读写操作,只会在释放垃圾时覆盖某些域,因此减少了内存中需要修改的字节数,减少了对高速缓存的写入,今儿减少需要写回内存的数据量
通常仅适用于单线程环境,因为多线程同时修改位图可能存在较大的写冲突风险
延迟清除 清除垃圾腾出的空间能立即得到复用,因而提升了程序的局部性;将标记-清除算法的复杂度降低到与堆中存活对象成正比的水平 延迟清除的效果是不均衡的,如果可回收对形成一片连续的空间,那么效率很高;如果进入大批活动对象范围,则很可能多次查询失败要直到遍历整个堆才找到合适的空间

标记-整理算法

算法 优点 局限性
Two-Finger 算法 简单快速,每次遍历过程操作较少;
forwarding 指针在对象移动后才写入,不会有信丢失,因此无需占用额外的空间来记录 forwarding 指针;
内存访问模式可预测,支持预取,进而提升回收器的告诉缓存友好性;
重排对象为任意顺序,破坏了赋值器的局部性;
受制于“所有对象大小最好一致”的条件,如果对象大小不一致,在移动过程中还是难以避免产生碎片;
Lisp2 算法 可有效利用堆; 需要在对象头中占用额外空间保存 forwarding指针;
整理过程需要进行三次遍历;

复制算法

算法 优点 局限性
递归算法 深度优先遍历,有引用关系的对象会被安排在堆中离彼此较近的位置,有利于提高高速缓存命中率; 递归调用函数消耗栈空间;
Cheney算法 采用迭代实现,消除了递归带来的额外负担和栈消耗;
直接把堆空间作为队列,省去了额外的内存空间开销;
由于广度优先遍历的特点,Cheney复制算法中无法将有引用关系的对象复制到相邻的位置,降低了高速缓存的命中率;

引用计数法

算法 优点 局限性
延迟引用计数(类似还包括合并引用计数) 消除了操作局部变量时引用计数变更带来的开销; 垃圾不能马上得到回收;
ZCT 与日志缓冲区等带来额外的空间开销;
没有解决循环引用的问题;
1-bit 引用计数法 不容易出现高速缓存缺失,因为它不需要在更新计数器时读取要引用的对象;不需要额外的给计数器留空间,节省了内存空间; 如果有大量计数器溢出的对象,会给堆造成巨大的负担,甚至很难保证分块;

以上只是列出了各类垃圾回收算法中典型的一两种实现,实际上多年来有很多研究针对每个算法的局限性做针对性的研究,还有非常多很细化的改进算法有待学习。

由于开这个系列的初衷,只是想拓宽一下自己对垃圾回收机制的了解,然而了解得越多越意识到这样一个主题若要深入研究下去,要学习的东西真的太多了。考虑到个人的精力,自己肯定做不到像研究生一样细啃论文,目前就先挖到这个尚浅的深度吧。


参考资料

  • 垃圾回收的算法与实现
  • 垃圾回收算法手册:自动内存管理的艺术