1.引用计数算法web
引用计数(Reference Counting)算法是每一个对象计算指向它的指针的数量,当有一个指针指向本身时计数值加1;当删除一个指向本身的指针时,计数值减1,若是计数值减为0,说明已经不存在指向该对象的指针了,因此它能够被安全的销毁了。能够很直观的用下面的图表示:算法
引用计数算法的优势在于内存管理的开销分布于整个应用程序运行期间,很是的“平滑”,无需挂起应用程序的运行来作垃圾回收;而它的另一个优点在于空间上的引用局部性比较好,当某个对象的引用计数值变为0时,系统无需访问位于堆中其余页面的单元,然后面咱们将要看到的几种垃圾回收算法在回收前都回遍历全部的存活单元,这可能会引发换页(Paging)操做;最后引用计数算法提供了一种相似于栈分配的方式,废弃即回收,后面咱们将要看到的几种垃圾回收算法在对象废弃后,都会存活一段时间,才会被回收。编程
引用计数算法有着诸多的优势,但它的缺点也是很明显的。首先能看到的一点是时间上的开销,每次在对象建立或者释放时,都要计算引用计数值,这会引发一些额外的开销;第二是空间上的开销,因为每一个对象要保持本身被引用的数量,必须付出额外的空间来存放引用计数值;引用计数算法最大的缺点就在于它没法处理环形引用,以下图所示:安全
此 处蓝色的这两个对象既不可达也没法回收,由于彼此之间互相引用,它们各自的计数值都不为0,这种状况对引用计数算法来讲是无能为力的,而其余的垃圾回收算法却能很好的处理环形引用。app
引用计数算法最著名的运用,莫过于微软的COM技术,大名鼎鼎的IUnknown接口:编程语言
其中的AddRef和Release就是用来让组件本身管理其生命周期,而客户程序只关心接口,而无须再去关心组件的生命周期,一个简单的使用示例以下:ide
上面的客户程序在CreateInstance中已经调用过AddRef,因此无需再次调用,而在使用完接口后调用Release,这样组件本身维护的计数值将会改变。下面代码给出一个简单的实现AddRef和Release示例:函数
在编程语言Python中,使用也是引用计数算法,当对象的引用计数值为0时,将会调用__del__函数,至于为何Python要选用引用计数算法,据我看过的一篇文章里面说,因为Python做为脚本语言,常常要与C/C++这些语言交互,而使用引用计数算法能够避免改变对象在内存中的位置,而Python为了解决环形引用问题,也引入gc模块,因此本质上Python的GC的方案是混合引用计数和跟踪(后面要讲的三个算法)两种垃圾回收机制。oop
2.标记-清除算法性能
标记-清除(Mark-Sweep)算法依赖于对全部存活对象进行一次全局遍从来肯定哪些对象能够回收,遍历的过程从根出发,找到全部可达对象,除此以外,其它不可达的对象就是垃圾对象,可被回收。整个过程分为两个阶段:标记阶段找到全部存活对象;清除阶段清除全部垃圾对象。
标记阶段
清除阶段
相比较引用计数算法,标记-清除算法能够很是天然的处理环形引用问题,另外在建立对象和销毁对象时时少了操做引用计数值的开销。它的缺点在于标记-清除算法是一种“中止-启动”算法,在垃圾回收器运行过程当中,应用程序必须暂时中止,因此对于标记-清除算法的研究如何减小它的停顿时间,而分代式垃圾收集器就是为了减小它的停顿时间,后面会说到。另外,标记-清除算法在标记阶段须要遍历全部的存活对象,会形成必定的开销,在清除阶段,清除垃圾对象后会形成大量的内存碎片。
3.标记-缩并算法
标记-缩并算法是为了解决内存碎片问题而产生的一种算法。它的整个过程能够描述为:标记全部的存活对象;经过从新调整存活对象位置来缩并对象图;更新指向被移动了位置的对象的指针。
标记阶段:
清除阶段:
标记-压缩算法最大的难点在于如何选择所使用的压缩算法,若是压缩算法选择很差,将会致使极大的程序性能问题,如致使Cache命中率低等。通常来讲,根据压缩后对象的位置不一样,压缩算法能够分为如下三种:
1. 任意:移动对象时不考虑它们原来的次序,也不考虑它们之间是否有互相引用的关系。
2. 线性:尽量的将原来的对象和它所指向的对象放在相邻位置上,这样能够达到更好的空间局部性。
3. 滑动:将对象“滑动”到堆的一端,把存活对象之间的自由单元“挤出去”,从而维持了分配时的原始次序。
4.节点拷贝算法
节点拷贝算法是把整个堆分红两个半区(From,To), GC的过程其实就是把存活对象从一个半区From拷贝到另一个半区To的过程,而在下一次回收时,两个半区再互换角色。在移动结束后,再更新对象的指针引用,GC开始前的情形:
GC结束后的情形:
节点拷贝算法因为在拷贝过程当中,就能够进行内存整理,因此不会再有内存碎片的问题,同时也不须要再专门作一次内存压缩。,而它最大的缺点在于须要双倍的空间。
5.总结
本文总共介绍了四种经典的垃圾回收算法,其中后三种常常称之为跟踪垃圾回收,由于引用计数算法可以平滑的进行垃圾回收,而不会出现“中止”现象,常常出现于一些实时系统中,但它没法解决环形问题;而基于跟踪的垃圾回收机制,在每一次垃圾回收过程当中,要遍历或者复制全部的存活对象,这是一个很是耗时的工做,一种好的解决方案就是对堆上的对象进行分区,对不一样区域的对象使用不一样的垃圾回收算法,分代式垃圾回收器正是其中一种,CLR和JVM中都采用了分代式垃圾回收机制,但它们在处理上又有些不一样,后面的文章再详细介绍这两种垃圾回收器的区别。
更加详细请参见于:http://www.cnblogs.com/Terrylee/