垃圾回收的算法的选择!?
最近在看《C++编程艺术》,这本说讲的很深刻,讲到了垃圾回收算法!看着很是爽!就将一个信息摘录出来,供大家学习!
选择垃圾回收的算法:
在为C++实现垃圾回收器之前,有必要确定垃圾回收使用的算法。在这里介绍三种典型的方法:引用计数、标记并清除、复制。在确定选择哪种方法的同时,我们先来浏览一下这三种算法:
引用计数:
在引用计数中,每一块动态分配的内存都与一个引用计数有关。这个计数在每次对内存的引用增加的时候增加1,在取消对内存的引用是减1.用C++的术语来说,这意味着每次将一个指针指向一块已分配内存的时候,与内存相关的引用计数增加1.当这个指针指向其他位置的时候,引用计数减1.当引用计数下降为0的时候,内存不再被使用,从而可以释放。
引用计数的最大优点是其简单性----易于理解并实现。另外,它的位置不受堆结构的影响,因为引用计数不依赖于对象的物理位置。引用计数增加了每个指针操作的开销,但是回收阶段的开销相对较低。其主要的缺点是循环的引用阻止了其他不再使用的内存的释放。当两个对象互相指向对方的时候(无论是直接的还是间接的),就会发生循环引用。在此情况下,对象的引用计数永不为0.为了解决循环引用的问题,设计了一些解决方案,但是这些方案都会增加复杂程度和开销。
标记并清除:
标记并清除涉及到两个阶段。在第一个阶段,堆中的所有对象都被设置为未标记状态。然后,可以由程序变量直接或者间接访问的所有对象都被标记为“正在使用”。在第二个阶段,扫描所有已分配的内存(也就是说,进行了内存的清除),会释放所有未标记的元素。
标记并清除有两个主要优点:
首先,他很容易处理循环引用;
其次,在回收之前,他实际上没有增加运行时开销;
标记并清除也有两个主要的缺点:
首先,由于在回收的时候必须扫面整个堆,因此回收垃圾可能会花费较多的时间。因此,对于某次额程序,垃圾回收可能会导致程序运行效率低下;
其次,尽管标记并清除在概念上很简单,但是要有效地实现它并非易事;
复制:
复制算法将自由内存分到两个空间中。一个是活动空间(持有当前的堆),一个是空闲空间。在垃圾回收期间,活动空间中正在使用的对象被确认,并复制到空闲空间中。然后,两个空间的角色反转,空闲空间变为活动空间,活动空间变为空闲空间。提供了复制过程中压缩堆的优点。它的缺点是在某个时刻只允许使用一个的自由内存。
采用哪种算法:
三种垃圾回收的经典算法都各自的优缺点,好像很难做出选择。然而,考虑前面列举出的限制,就会得出明显的选择:引用计数。最重要的是,引用计数可以很容易地应用与现有的C++动态分配系统上。其次,他可以以一种直接的方式来实现,而不会影响代码。第三,它不需要堆的任何特定的组织或者结构,从而不会影响C++提供的标准分配系统。
使用引用计数的一个缺点是很难处理循环引用。这对于许多程序而言,并不是一个问题,因为有意的循环引用并不常用,并且可以避免。(即使我们所说的循环,如循环队列,也不一定要用到循环指针引用)。当然,某些情况下需要使用循环引用。也可能建立了循环引用,而您并不知道,特别是使用第三方库的时候。因此,垃圾回收器必须提供某种方法来适度地处理循环引用。
为了处理循环引用问题,释放任何已经分配的内存。这将确保涉及到循环引用的对象被释放,并且调用他们的析构函数。通常在程序结束的时候,不应该再有已分配的对象,理解这一点很重要。对于涉及到循环引用而不能被释放的对象,这种机制是显示的。
实现垃圾回收器
为了实现引用计数的垃圾回收器,必须有某种方法来跟踪指向每块动态分配的内存的指针的数量。问题在于,C++没有内建的机制来确保一个对象知道其他的对象何时指向他。
幸运的是,在此有一个解决方案:可以建立一个新的支持垃圾回收的指针类型。
为了支持垃圾回收,新的指针类型必须做三件事情:
- 它必须为使用中的动态分配的对象维护一个引用计数的链表;
- 它必须跟踪所有的指针运算符,每次某个指向一个对象时,都要使这个对象的引用计数增加1,每次某个指针重新指向其他对象的时候,都要使这个对象的引用计数减1;
- 它必须回收那些引用计数为0的对象。除了支持垃圾回收之外,这个新指针类型与普通的指针看起来一样;