垃圾收集是编程语言中 使用的内存管理技术之一。 它是一种自动内存管理技术,作为许多编程语言的功能添加。垃圾收集器收集或回收分配给变量或对象但不再被程序使用的内存; 这也称为垃圾。

三种基本的垃圾收集算法及其改进算法

1、引用计数算法

引用技术算法是唯一一种不用用到根集概念的垃圾回收算法。

基本思路是为每个对象加一个计数器,计数器记录的是所有指向该对象的引用数量。

每次有一个新的引用指向这个对象时,计数器加一;反之,如果指向该对象的引用被置空或指向其它对象,则计数器减一。当计数器的值为0时,则自动删除这个对象。

2、 Mark & Sweep (标记清除)算法

标记清除算法,是目前公认的最有效的GC方案。Mark&Sweep垃圾收集器由标记阶段和回收阶段组成,标记阶段标记出根节点所有可达的对节点,清除阶段释放每个未被标记的已分配块。

通过Mark&Sweep算法动态申请内存时,先按需分配内存,当内存不足以分配时,从寄存器或者程序栈上的引用出发,遍历上述的有向可达图并作标记(标记阶段),然后再遍历一次内存空间,把所有没有标记的对象释放(清除阶段)。

垃圾收集器将存储器视为一张有向可达图。

图中的节点可以分为两组:根节点,对应于不在堆中的位置,可以是寄存器、栈中的变量。另外一组称为堆节点,对应于堆中一个分配块,如下图:

C++面向对象:C++的垃圾回收_内存管理

当存在一个根节点可到达某个堆节点时,称该堆节点是可达的,反之称为不可达。

不可达堆节点为垃圾。可见垃圾收集的目标即是从从根集出发,寻找未被引用的堆节点,并将其释放。

相比较引用计数算法,标记-清除算法可以非常自然的处理环形引用问题,另外在创建对象和销毁对象时少了引用计数器的开销。

它的缺点在于标记-清除算法需要研究如何减少它的停顿时间,而分代式垃圾收集器就是为了减少它的停顿时间。

另外,在清除阶段,清除垃圾对象后会造成大量的内存碎片需要遍历所有的存活对象,这样会造成一定的时间开销,在清除阶段,清除垃圾对象后会造成大量的内存碎片。

3、 节点复制算法

    Mark & Sweep算法的缺点是在分配大量对象时,且对象大都需要回收时,回收中断过程可能消耗很大。而节点复制算法则刚好相反,当需要回收的对象越多时,它的开销很小,而当大部分对象都不需要回收时,其开销反而很大。
算法的基本思路是这样的:从根节点开始,被引用的对象都会被复制到一个新的存储区域中,而剩下的对象则是不再被引用的,即为垃圾,留在原来的存储区域。释放内存时,直接把原来的存储区域释放掉,继续维护新的存储区域即可。过程如图:

C++面向对象:C++的垃圾回收_垃圾收集器_02

可以看到,当被引用对象(非垃圾对象)很多时,需要复制很多的对象到新存储区域。

分代回收

以上三种基本算法各有各的优缺点,也各自有许多改进的方案。通过对这三种方式的融合,出现了一些更加高级的方式。而高级GC技术中最重要的一种为分代回收。它的基本思路是这样的:程序中存在大量的这样的对象,它们被分配出来之后很快就会被释放,但如果一个对象分配后相当长的一段时间内都没有被回收,那么极有可能它的生命周期很长,尝试收集它是无用功。为了让GC变得更高效,我们应该对刚诞生不久的对象进行重点扫描,这样就可以回收大部分的垃圾。为了达到这个目的,我们需要依据对象的”年龄“进行分代,刚刚生成不久的对象划分为新生代,而存在时间长的对象划分为老生代,根据实现方式的不同,可以划分为多个代。

一种回收的实现策略可以是:首先从根开始进行一次常规扫描,扫描过程中如果遇到老生代对象则不进行递归扫描,这样可大大减少扫描次数。这个过程可使用标记清除算法或者复制收集算法。然后,把扫描后残留下来的对象划分到老生代,若是采用标记清除算法,则应该在对象上设置某个标志位标志其年龄;若是采用复制收集,则只需要把新的存储区域内对象设置为老生代就可以了。

C++垃圾回收机制

Java 等语言和大多数脚本语言都将垃圾收集作为语言的一部分,以提高效率。在像 C++ 这样的语言中,必须手动内存管理,使用 new、delete 或某些算法等命令手动执行内存管理。

动态内存分配是在运行时在堆区域中分配的一种内存。 一旦停止使用该内存,就必须将其释放;否则,可能会导致内存泄漏。C++ 中的内存分配和释放使用 new 或 delete 等命令手动完成的。 一旦使用完毕,使用delete关键字来释放并清除内存。 在内部,它调用析构函数来销毁内存。因此,new 后面应该始终跟有 delete 命令,以避免内存泄漏。

C语言本身没有提供GC机制,而C++ 0x则提供了基于引用计数算法的智能指针进行内存管理。也有一些不作为C++标准的垃圾回收库,如著名的Boehm库。借助其他的算法也可以实现C/C++的GC机制,如前面所说的标记清除算法。

即使当时(以及现在)主流C++编译器并没有默认启用垃圾回收功能,设计C++11时仍考虑到未来可能的兼容性和扩展性,让未来的垃圾回收实现能够无缝集成到C++程序中,不影响现有的new和delete语义。