Java语言规范没有明确地说明JVM使用哪种垃圾回收算法,但是任何一种垃圾回收算法一般要做2件基本的事情:
1)发现无用信息对象;
2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。
本文只讨论垃圾回收算法,对如何判断对象是可被回收的感兴趣请参考可达性分析算法,对垃圾回收器感兴趣的具体收集器请参考垃圾回收器。
1、标记-清除算法(Mark-Sweep)
标记-清除算法是最基础的收集算法,后续的算法都是基于这种算法的思路并对其不足进行改进得到的。
算法分为两个阶段:标记阶段和清除阶段。标记阶段是标记出所有需要回收的对象,清除阶段是标记完成后统一回收所有标记的对象。标记过程使用可达性分析算法。
标记——清除算法的缺点:
1)效率问题,标记和清除两个过程效率都不高;
2)空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后程序运行过程中需要分配较大对象时,无法找到足够连续的内存而不得不提前触发另一次垃圾收集动作。
2、复制算法(copying)
该算法的提出是为了解决标记-清除算法的空间问题。
算法开始时把堆分成一个对象面和多个空闲面,程序从对象面为对象分配空间,当对象满了,基于复制算法的垃圾收集就扫描活动对象,并将活动对象复制到空闲面,这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。复制过程中用户程序暂停执行。
复制算法的优点:实现简单,运行高效且不容易产生内存碎片;
缺点:对内存空间的使用做出了高昂的代价,可用内存明显减少。
算法的应用:
很明显,复制算法适用于内存中对象活率不高的情况。所以现在的商业虚拟机都采用这种收集算法来对新生代的垃圾回收。
3、标记-整理算法(Mark-Compact)
标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。
标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。
算法应用:
很明显,这种算法适用于被回收的对象比较少的情况。所以老年代一般会使用标记-整理算法来进行垃圾回收。
4、分代收集算法(Generational Collection)
分代收集算法,更应该说是一种思想而不是具体的算法。这是一种JDK7中提出的、并被主流采用的一种垃圾收集思想。它基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。
在新生代每次垃圾回收都会有大批对象死去,只有少量存活,所以一般采取Copying算法,只需要采用少量存活对象的复制成本就可以完成收集;而老年代由于对象存活率高、没有额外空间进行分配担保,就必须使用标记-整理算法。
另外,需要注意的是,在堆区之外还有一个“持久代”,它用来存储class类、常量、方法描述等。对持久代的回收主要回收两部分内容:废弃常量和无用的类。这个所谓的“持久带”,严格来说并不属于分代收集的范畴,因为它不属于堆内存。