垃圾回收(GC) 的基本算法 (2)

垃圾回收(GC) 的基本算法

长此以往,我们会发现内存就会有一个一个的洞,碎片化会很严重,导致内存的利用率逐渐下降。同时,因为这里的内存是一块一块的,所以我们用链表来保存它的时候,分配内存查找又是一个问题,所以就很麻烦。

此外,周期性得标记对象,从而会周期性得改变对象的微小数据,所以导致操作系统 COW 体系不能得到较好的运用,从而导致性能的缺失。这是一方面,前面还有一个问题,那就是我们标记对象的时候以怎么样的顺序来查找活动对象,常见的查找方式有深度优先查找广度优先查找,这两种查找在性能上可能没有太大区别,但是,对于临时空间的占用却是有较大的影响,所以一般来说,深度优先广度优先更能压低内存使用量,所以经常使用的是深度优先搜索

虽然有缺点,但是标记-清除的优点也是比较明显的,例如实现起来还是比较简单的,与保守式 GC 是兼容的,使得 标记-清除 算法在实际应用中还是得到大家的青睐的。

2. 引用计数

除了标记-清除算法外,引用计数 也是一种不错的方法,引用计数算法 顾名思义就是在对象中额外记录自身被引用的次数,当次数减小到 0 的时候那么就知道自己已经没有用处了,可以被回收了。也是一种很简单很直观的方式,可以在对象不被使用的时候立刻回收掉内存,从而将垃圾回收的时间分散化,也不需要像 标记-清除 一样需要进行遍历查找。

但是这也带来了一定程度的麻烦,例如,我们需要使用内存屏障管理引用计数,对象的生成、赋值和引用都涉及引用计数的变化,从而导致引用计数的增减处理频繁;同时,因为引用计数的存在,我们还需要在对象的自身数据之外,为引用计数分配固定的空间来存放计数,这是固有损耗。还有一个致命的缺点就是,使用引用计数算法,无法清除 循环引用 的问题,从而导致内存一直占用,无法释放。

3. GC 复制

前面介绍的两种方法都是在对象本身上操作的,也就是说清除和释放都是操作对象本身所在的位置,但是,GC 复制算法 就稍微复杂一些了,GC 复制算法 最原始的做法就是将内存一分为二,每次只使用其他一半,当要 GC 的时候就将使用着的一半中的活动对象复制到另外一半中,然后清理掉这一半中的所有对象,直接使用另外一半即可,重复这个操作。

垃圾回收(GC) 的基本算法

这个我们一眼就可以看出问题,那就是空间的利用率不高,但是,好处也是非常明显的,首先是速度快,没有额外的标记-清理操作,就是直接的复制,高吞吐;分配对象直接分配,不需要考虑碎片化问题;还可以保持与 OS 的缓存兼容,优势还是比较明显的。然而,硬币总有正反面,除了空间利用率不高之外,这种方法不兼容保守式的 GC 算法,此外,对于递归调用还会有栈溢出的风险。

所以为了更好得完善了这个算法,还有有很多改进思路被提出的,例如不是将空间划分为两部分,而是划分为多个部分,从而提升空间的利用率就是其中的一个思路。

总结

本文就常见的三种垃圾回收基本算法以及经常需要考虑的几个性能指标进行介绍,从而为了解垃圾回收开一个头。其实看各种编程语言的 GC 实现都会发现本文中基本算法的身影,无非就是它们直接如何组合,所以,理解本文中的基本算法对于理解其他编程语言的 GC 实现还是很有帮助的。

Reference

Garbage Collection

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxfzg.html