定义
我们把之前使用过,现在不再使用或者没有任何指针再指向的内存空间称为“垃圾”。而将这些“垃圾”收集起来以便再次使用的机制叫做“垃圾回收机制”
在大多数编程语言中,我们都可以看到对垃圾回收特性的支持,如下表所示:
编程语言 | 对垃圾回收的支持情况 |
C++ | 部分 |
Java | 支持 |
Python | 支持 |
C | 不支持 |
C# | 支持 |
Ruby | 支持 |
PHP | 支持 |
Perl | 支持 |
Hashkell | 支持 |
分类
垃圾回收的方式虽然很多,但主要可以分为两大类:
基于引用计数的垃圾回收器
简单的说,引用计数主要是使用系统基类对象被引用(引用、指针)的次数。当对象被引用的次数变为0时,该对象既可视为”垃圾“而回收。使用引用计数作垃圾回收算法的一个优点是简单,与其他垃圾回收算法相比,该方法不会造成程序暂停,因为计数的增减与对象的实用是紧密结合的。此外,引用计数也不会读系统的缓存或者交换空间造成冲击,因为被认为“副作用”比较小。但是这种方法很难处理“环形引用”问题,此外由于计数带来的额外开销也不小,所以在使用上也有一定的限制。
基于跟踪处理的垃圾回收器
相比于引用计数,跟踪处理的垃圾回收机制被更广泛的应用。其基本方法是产生跟踪对象的关系图,然后进行垃圾回收。使用跟踪方式的垃圾回收算法主要有以下几种:
标记-清除
这个算法可以分为两部分。首先该算法将程序中正在使用的对象视为“根对象”,从根对象开始查找它们所引用的堆空间,并在这些堆空间上做标记。当标记结束之后,所有被标记的对象就是可达对象或者活对象,而没有被标记的对象就认为是垃圾,在第二步的清扫阶段就会被回收掉。
这种方法的特点是活对象不会被移动,但是其存在会出现大量的内存碎片的问题。
标记-整理
和上面一样,但是标记完之后,不再遍历所有对象清扫垃圾了,而是将活的对象向“左”靠齐,这就解决了内存碎片的问题
标记-整理方法有个特点就是移动活的对象,因此,相应的,程序中所有对堆内存的引用都必须更新
标记-拷贝
这种算法将堆空间分为两个部分:from和to。刚开始系统只从from的堆空间里面分配内存,当from分配满的时候系统就开始回收垃圾:从from堆空间中找出所有活对象,拷贝到to的堆空间中。这样一来,from的堆空间就只剩下垃圾了。而对象被拷贝到to里面之后,在to里面是紧密排列的。接下来只是需要将from和to换一下角色,接着从新的from里面开始分配。
标记-拷贝算法的一个问题是堆的利用率只有一半,而且也需要移动活的对象。此外,从某种意义上讲,它也是标记-整理算法。