(译)可视化垃圾收集(GC)算法

友情提示:非原文链接可能会影响您的阅读体验,欢迎查看原文。(http://blog.geekcome.com)


原文地址:

http://spin.atomicobject.com/2014/09/03/visualizing-garbage-collection-algorithms/

 

绝大部分开发者都认为自动垃圾回收是理所当然的。不过这只是一个令人惊讶的特点,可以在运行时让我们的程序运行管理更简单。

 

但是如果你深入研究一个现代语言的的垃圾收集器,是很难弄明白它实际是如何工作的。如果你对它是如何实现的或者他们为什么会出错没有一个好的理解,那么会有上千种实现细节会令你头晕。

 

我对5种不同的垃圾回收算法制作了几个可视化的工具。项目在github:https://github.com/kenfox/gc-viz 。它是如此简单的揭示了这些重要算法的运行过程令我非常惊讶。

 

程序结束时回收

最简单的垃圾回收就是在一个任务退出的时候来释放它动态申请的内存。这个是一个很有用的技术,特别是如果你可以把一个任务拆分为不同的分片的时候。大名鼎鼎的Apapche web服务器在启动的时候对每个请求创建一个小的内存池,并且在这个请求完成的时候释放内存池。

上图所示就是一个程序的运行过程。整个图代表的是内存。开始所有的内存都是黑色的,代表未被使用过。亮绿色或黄色代表这块内存正在被读取或写入。颜色变化可以说明一段时间内存的使用过程和现在的活动状态。如果仔细观察,你可以看到从最后申请内存地方开始申请新的内存。前面申请的内存就成为了内存垃圾–它没有被使用,并且不能被程序再次访问。

这个程序很简单的使用内存,不用为清理内存做任何担心。

 

引用计数回收

另一个简单的方法就是保持对一个变量引用的计数,并且当这个计数变为0时,释放该内存。对于已经存在的系统,这个是最常见添加内存回收机制–这个垃圾回收机制可以很简单的和原来的代码整合。Apple就是使用这个方式发布了Objective-C的垃圾回收器。

这次程序会在引用计数变为0的时候将其占用的内存释放。红色的闪烁代表了该内存引用在发生变化。这个的优点是内存垃圾会被立刻清理。

 

不幸的是,这个方式存在很多的问题。最严重的是,它无法消除环形引用。这个是非常普遍的–任何一个有父类引用或反向的引用都会形成一个环形引用,从而导致内存泄露。引用技术方式还有比较高的额外花销–在上图可以看到,即使再内存没有变化增加的情况下,仍经常有红色的点再闪烁。在现在的CPU中,算法的运行是非常快的,但是内存访问相比CPU是非常慢的,引用技术会经常加载和写入内存。所有的引用计数也会增加只读或者线程安全变量的访问。

 

标记-清除回收

标记-清除回收机制解决了引用计数存在的一些问题。它可以很容易的处理环形引用,并且不需要维护所有引用的计数,所以开销也很小。

它放弃了立刻进行垃圾收集的方式。在上图可以看到有一段时间内没有红色点闪烁,然后突然一系统的红色点闪烁,标明正在对内存进行标记。在标记结束后,它会清除所有标记的内存,并且回收垃圾。你也会看到–几块内存区域同时变为黑色(可用内存)。

这种回收机制比引用计数方式需要实现更多的一致性,也更难和一个已经存在的系统整合。标记阶段需要遍历所有的数据,即使数据被打包在另外的结构或对象中。如果一个对象不提供访问机制,那就很难和已有的代码整合。该回收机制的另一个缺点是标记过程必须遍历所有的内存来找到垃圾。对于没有多少垃圾的系统来说,这个不是问题,但是如果的函数式编程方式会产生大量的垃圾。

 

标记-整理回收

你可能会注意到,上面的回收机制中,从来没有对象的移动。一个对象一旦在内存中分配了,即使它像海中的岛一样被黑色包围,它都不会移动。下面的两个算法使用了不同的方法改变了这种方式。

该垃圾回收方式不只是释放占用的内存空间,还会将内存中的对象整理移动。内存中的对象总是保持原来的顺序–一个在另一个对象之前分配的对象总是处于较低地址的内存位置–但是由于对象的内存释放产生的内存碎片会被整理。

这种内存整理的方式意味着总是在使用内存的末尾分配内存。这被称作“块分配”并且和栈内存分配一样高效。一些使用这种垃圾回收机制的系统中甚至不需要调用栈来存储数据,只需要像对待其他任何对象一样在堆中分配内存即可。

另一个好处就是(可能更多的是一种理论)当对象被压缩后,程序会更方便的访问内存(对现代内存中的硬件缓存更友好)。尽管你可能发现不了这个优点–在引用计数和标记-清除垃圾回收机制中内存分配是非常复杂的。标记-整理回收机制更容易调试和更高效。

标记-整理回收是一个复杂的算法,需要多次扫描所分配的对象。在上面的动画可以看到存活的对象在多次读写后被标记,对象被移动并且最后引用会被固定到对象所移动到的位置。这个复杂度带来的好处就是操作极小的资源消耗。JVM利用了几种不能的垃圾回收算法。年老区的对象使用该算法进行内存回收。

 

复制垃圾收集

最后一中垃圾收集机制是多数高效垃圾回收系统的基础。它像标记-整理回收类似是一个移动的回收器,但是它非常简单。它利用两块内存空间并且简单的复制存活的对象。实际上,不只是有两块内存区,这些内存块用于对象的不同阶段。新的对象在一块空间中创建,如果存活下来就复制到另一个空间,如果该对象长时间存活会复制到老年区内存空间。如果你听说一个垃圾回收器用不同的生命时期来描述,那通常是多空间复制内存收集。

不止是简单和高效,该算法最主要的优势是仅仅在存活对象上花费时间。不存在分离的标记区域来进行清理或整理。在活对象扫描的时候被立刻复制,并且对象的引用会在接下来修补过程执行。

在上面动画中,可以看到几次收集过程几乎所有的数据都被从一个区域复制到另一个区域。这正是这个算法的可怕之处。如果你可以控制你的内存并且调整你的内存分配以至于在内存回收的时候大多数对象生命已经结束,这时你会获得一个既安全又高效的内存回收效果。

 

作者: Yong Man
出处: 极客来 GeekCome
提示:本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
如果对文章有任何问题,都可以在评论中留言,我会尽可能的答复您,谢谢你的阅读

猜你喜欢

转载自blog.csdn.net/yming0221/article/details/39087795