1、标记-清除算法

最基础的算法,分为标记和清除两个阶段:首先标记出所有需要回收的对象,完成后统一回收掉所有被标记的对象。后续的算法都是基于此算法来改进的。
主要有两个缺点:

  • 效率问题:标记和清除的过程效率都不高。
  • 空间问题:标记清除之后会产生大量不连续的内存碎片,这样可能会导致以后再运行过程中需要分配大对象时无法找到足够的连续内存而不得不触发新的一次垃圾回收。

标记-清除算法图示-1
垃圾回收算法 - 图1

2、复制算法

为了解决效率问题,复制算法出现了。将内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存使用完了,就讲存活着的对象复制到另一块上面,然后把使用过的内存空间一次清理掉。
这样每次都是对一块内存进行回收,分配时也不考虑内存碎片等复杂情况。代价就是将内存缩小为原来的一半。
复制算法图示-2
image.png
现在的商业虚拟机都是采用这种收集算法来回收新生代。新生代中的对象98%是朝生夕死,所以并不需要按照1 : 1的比例来划分空间,而是分成一块较大的Eden区和两块较小的Survivor区。每次使用Eden区和其中的一块Survivor区。当回收时,将Eden区和Survivor中还活着的对象一次性拷贝到另外一块Survivor空间上,然后清除Eden区和用过的Survivor区。

3、标记-整理算法

复制收集算法在对象存活率较高时需要执行很多的复制操作,效率很低。所以在老年代里一般不能直接选用这种算法。
标记整理算法的标记过程和标记清除算法是一样,后续不是直接对可回收对象进行清除,而是将所有存活的对象都向一端移动,然后清理掉端边界以外的内存。
标记-整理算法图示-3
垃圾回收算法 - 图3

4、分代收集算法

当前商业虚拟机的垃圾收集都采用 “分代收集” 算法,这种算法就是根据对象的存活周期的不同将内存划分为几块。一般是把Java对分为新生代和老年代,这样就根据各个年代的特点采用适当的收集算法。
在新生代中,每次垃圾回收时都有大量的对象死亡,只有少量的存活,所以选用复制算法,只需要付出少量的存活的复制成本就可以完成回收。
在老年代中,因为对象的存活率高、没有额外空间对它进行分配担保,那就必须使用”标记-清理”,”标记-整理” 的算法进行回收。