各种编程语言的实现都采用了哪些垃圾回收算法?这些算法都有哪些优点和缺点?

关注者
1204
被浏览
46752

20 个回答

垃圾收集算法是个很大的话题。首先要明确的是,垃圾收集算法和语言不一定是绑定的。比如 Java,不同的 JVM 实现可能采用不同的算法。其次,垃圾收集算法数量庞大,一一列举是不可能的,篇幅所限这里只能给个非常概略的介绍。如果希望对垃圾收集相关算法有个全景式的了解,请参阅本人的译作,垃圾收集 (豆瓣)

==== 转入正文的分割线 ====

从各种垃圾收集算法最基本的运行方式来说,大概可以分成三个类型:

1. 引用计数(reference counting):

基本思路是为每个对象加一个计数器,记录指向这个对象的引用数量。每次有一个新的引用指向这个对象,计数器加一;反之每次有一个指向这个对象引用被置空或者指向其他对象,计数器减一。当计数器变为 0 的时候,自动删除这个对象。

引用计数的优点是 1)相对简单,不需要太多运行时(run-time)的支持,可以在原生不支持 GC 的语言里实现。2)对象会在成为垃圾的瞬间被释放,不会给正常程序的执行带来额外中断。它的死穴是循环引用,对象 A 包含一个引用指向对象 B ,同时对象 B 包含一个引用指向对象 A,计数器就抓瞎了。另外,引用计数对正常程序的执行性能有影响(每次引用赋值都要改计数器),特别是在多线程环境下(改计数器要加锁同步)。

现在仍然主要采用引用计数的例子有 Apple 的 ARC,C++ 新标准里的 std::shared_ptr。

2. 标记-清扫(mark-sweep)。

基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。

标记-清扫没有无法处理循环引用的问题,不触发 GC 时也不影响正常程序的执行性能。但它的问题是当内存耗尽触发 GC 时,需要中断正常程序一段时间来清扫内存,在内存大对象多的时候这个中断可能很长。

采用或者部分采用标记-清扫的例子非常多,不一一列举了。

3. 节点复制(copying)。

基本思路是把整个内存空间一分为二,不妨记为 A 和 B。所有对象的内存在 A 中分配,当 A 塞满的时候,同样从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象复制到 B 去,然后对调 A 和 B 的角色。

相对于标记-清扫,节点复制的主要缺点是总有一半空间空闲着无法利用,另一个比较隐晦的缺点是它使用内存的方式与现有的内存换页、Cache 换入换出机制有潜在的冲突。但它有个很大的优点: 所有的对象在内存中永远都是紧密排列的,所以分配内存的任务变得极为简单,只要移动一个指针即可。对于内存分配频繁的环境来说,性能优势相当大。另外,由于不需要清扫整个内存空间,所以如果内存中存活对象很少而垃圾对象很多的话(有些语言有这个倾向),触发 GC 造成的中断会小于标记-清扫。

同样的,采用或者部分采用节点复制的例子也非常多,不一一列举了。

==== 基本算法介绍完毕的分割线 ====

以上三种基本算法各有优缺点,也各有许多改进的方案。目前工程实践上最为成功的方案应该要算分代(generational)垃圾收集。它的基本思路是这样的:程序中存在大量的临时对象,分配出来之后很快就会被释放,而同时如果一个对象分配出来之后相当长的一段时间内都没回收,那么极有可能它的生命周期很长,尝试收集它会是无用功。所以可以把内存有意识地按“对象年龄”分成若干块,不妨记为老中青(XD),所有的分配都在青代进行,青代塞满只对青代做 GC,然后把存活下来的对象移动到中代,直到中青代都塞满,再把存活下来下来的对象移动到老代 —— 这只是个思路上的例子,实践中分代式垃圾收集算法的方案五花八门,而且常常同时使用了不止一种基本算法(比如青代用节点复制,老代用标记清扫啥的)。
我来回答下PHP的吧, 在5.3以前PHP采用经典的引用技术方法, 这种方法简单, 回收期分散, 不会造成程序的长时间挂起. 然而缺点就是不能解决循环引用. 这个问题对于PHP来说本身不是什么大问题, 因为经典的应用场景下, 一次请求结束以后, 所有属于这个请求的内存就会被释放. 但是随着PHP的应用场景越来越广, 一些后台脚本, 服务也开始需要用PHP来编写, 这样一来, PHP就需要解决循环引用问题.

在PHP5.3的时候, 引入了一套额外的垃圾回收算法: <Concurrent Cycle Collection
in Reference Counted Systems > research.ibm.com/people

作为对引用计数的一个补充, 这套新的逻辑叫做zend_gc. 具体实现可以参看: php.net/manual/zh/featu