理解垃圾回收算法

来自Atomic Object公司的Ken Fox为了解释各种垃圾回收算法,开发了一个小工具,用于对各种垃圾回收算法进行可视化演示。这个工具通过动画的形式展示了垃圾回收算法的执行过程,让人非常直观地了解这些复杂算法背后的原理。这篇文章最早由Ken Fox于2014年9月3号发表在Atomic Spin博客上,以下译文已获得源网站的翻译授权。原文链接“Visualizing Garbage Collection Algorithms”。

自动垃圾回收机制对于大部分开发人员来说已经见惯不怪了,它只不过是语言运行时为了简化我们的工作而提供的一种特性。

如果我们不了解垃圾回收器的原理,在选择垃圾回收器时,就很难做出决定。一个垃圾回收器涉及数百个实现细节,如果不清楚它们的作用和陷阱,就很容易陷入迷茫之中。

我开发了一个好玩的小工具,用于可视化5种垃圾回收算法。这个工具把运行时的垃圾回收行为创建成动画,相关代码可以在github上找到。这些垃圾回收算法可以通过简单的动画来演示,连我自己都感到很惊讶。

在结束时进行清理—无垃圾回收(No GC)

在任务处理结束时一次性清理所有的资源,这是最简单的垃圾清理方式。如果任务可以被拆分成更小的单元,那么使用这种方式来清理垃圾会很有效。举个例子,Apache的Web服务器为每个请求创建了一个小型的内存池,请求被处理完之后,整个内存池会被清理掉。

下面的动画模拟了一个正在运行的程序,画布代表程序的整体内存。黑色表示未被使用的内存,闪烁的绿色表示内存读取,黄色表示内存写入。色块会随着时间衰退,我们不仅可以看到内存是如何被使用的,还能看到当前的活动情况。如果仔细看的话,我们会发现,程序会忽略掉一些内存区域。这些区域变成了垃圾,它们不会再被使用,程序也无法触及到这些区域。

理解垃圾回收算法

这个程序在内存中运行,不需要担心垃圾的清理问题。在下面的其他例子中,我也将使用这个程序作为示例。

引用计数回收器(Reference Counting Collector)

另一个简单的方案是使用计数器,计数器表示资源(内存中的对象)的使用次数,当计数器变成零的时候就可以将该资源销毁。在向已有的系统添加垃圾回收器时,开发人员通常会选择计数回收器,因为这种方式最容易与已有的资源管理器和代码集成在一起。Apple最初在Object-C里使用了标记清理回收器,不过这种回收器给他们带来了很多困扰,后来他们不得不使用计数回收器替代它。而且事实证明,自动计数回收器比之前的标记清理回收器更适合在Object-C中使用。

下面的动画模拟的是之前的那个程序,不过这次是根据内存对象的引用次数来清理垃圾。闪烁的红色表示正在进行引用计数。引用计数的最大好处在于它可以很快地检测出垃圾,从动画中你会发现,有些红色闪过之后的区域立即变成黑色。

理解垃圾回收算法

不过引用计数算法存在很多问题。最大的一个问题是,它无法解决循环引用问题。这种情况很常见,父对象和反向引用会形成引用环,造成内存泄露。除此之外,引用计数需要额外的开销。从动画中可以看到,在内存使用没有增长的情况下,红色区域也一直保持闪烁。虽然现代CPU的运算能力很强,但内存并不快,而计数器的运算需要频繁使用内存。因为计数器需要不断被更新,所以它们不是只读的,而且不能保证线程安全。

引用计数器算法是一种摊销算法(将开销分摊给了程序),不过它是偶然性的摊销,无法保证响应时间。例如,假设程序正在处理一个很大的树结构,最后一个使用这个树的程序会触发销毁操作,根据墨菲定律,其延迟会比期望的要高。不过,这篇文章所演示的其他算法也都不是完全的摊销算法,所以偶然性的摊销完全取决于所处理的数据(这些算法有些是并发的,有些是部分并发的,不过我开发的这个工具无法演示这些特性)。

标记清除回收器(Mark Sweep Collector)

标记清除回收算法解决了一些在引用计数算法中存在的问题。它解决了循环引用问题,而且开销要小得多,因为它不需要维护计数器。

理解垃圾回收算法

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

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