打扫房间的各种方法 —— Java虚拟机的垃圾收集算法清单

带着问题阅读

  • 垃圾收集时,除了效率,还要考虑什么?
  • 如果你是JVM设计者,在标记完可回收的对象后,你会怎么去收集垃圾对象?


导语

通过前面两讲的学习,我们已经知道JVM是如何判断对象是不是可以回收,以及回收是如何发起的。那么发起之后又是如何进行收集的呢,收集的算法有哪些,这是我们这一讲的内容。

本文是Effective Java专栏Java虚拟机专题的第六讲,如果你觉得看完之后对你有所帮助,欢迎订阅本专栏,也欢迎您将本专栏分享给你身边的工程师同学。

在学习本节课程之前,建议您了解一下以下知识点:


标记-清除算法

标记-清除算法是最基础的垃圾收集算法,这个算法在收集垃圾时分两步:

  1. 标记所有需要回收的对象;
  2. 标记完成后统一回收所有被标记的对象。

之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其进行改进而得到的。

它的不足主要有两个:

  • 一是效率问题,这种逐个进行清除的算法,效率固然不高;
  • 另一个是空间问题,标记清除后会产生大量不连续的内存碎片,导致以后需要分配较大对象时,无法找到足够大的连续内存而不得不提前触发一次垃圾收集动作。

注:《深入理解Java虚拟机》中,关于效率问题,周老师的原文是“标记和清除的效率都不高”,这里个人理解,标记是其他算法也需要的,所以标记效率不高不可以作为不选择这个算法的理由。

可以用下面这张图来理解标记-清除算法:

打扫房间的各种方法 —— Java虚拟机的垃圾收集算法清单


复制算法

针对标记-整理算法的不足,一种称为“复制”的算法出现了,它的收集步骤如下:

  1. 将内存划分为大小相等的两块;
  2. 当其中一块内存用完时,把存活对象复制到另一块上去,然后把原来的那一块全部清理掉。

这种算法实现简单,运行高效,同时也不会有内存碎片,代价是需要将内存缩小为原来的一半。

可以用下面这张图来理解“复制”算法:

打扫房间的各种方法 —— Java虚拟机的垃圾收集算法清单

现代虚拟机都采用这种算法来回收新生代根据IBM的专门研究,新生代中的对象,98%是“朝生夕死”的,所以没有必要按照1:1来划分成两个一样大小的内存,而是将内存划分为一块大的Eden区和两块小的Survivor区域每次使用Eden和其中一块Survivor

回收时,把Eden和使用的那块Survivor中存活的对象,一次性复制到那块未使用的Survivor中,然后清理掉Eden和刚刚使用的Survivor。

HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是说,每次新生代中可用内存为90%(80% + 10%),只有10%的内存会被浪费。

那么假如一次回收时,超过了10%的对象存活,Survivor内存不够存放时,该怎么办?

这时候就需要老年代来进行分配担保(Handle Promotion).

内存分配担保是指:如果另一块Survivor没有足够空间存放上一次新生代垃圾收集后留下来的存活对象,那么这些对象直接进入老年代。关于分配担保,后续课程还会详细介绍。


标记-整理算法

复制算法在对象存活率较高时就要做很多的复制操作,并且会经常需要分配担保,因此,这种算法是不适用于老年代的。

根据老年代存活率高的特点,有人提出了“标记-整理”算法,算法和“标记-清理”算法类似,但是标记之后,不直接对标记为可回收的对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界外的内存

可以用下面这张图来理解标记-整理算法

打扫房间的各种方法 —— Java虚拟机的垃圾收集算法清单


换个角度理解这几种算法

相信很多人看到这里,对于上面提到的几种算法的优劣,内存碎片那一块还是都可以理解的。比较难理解的是效率的问题,为什么“标记-整理”和“复制”算法就比“标记-清除”效率高呢?

我们可以这样理解,假设我们要清理一个文件夹中的文件:

“标记-清理”算法会这样做:先把要删除的文件,文件名前面加个“toDelete_”,表明是要删除的文件,然后对所有toDelete*的文件,逐个点击-右键-删除;在Linux上就是 rm -rf toDelete_file1.txt rm -rf toDelete_file2.txt rm -rf toDelete_file3.txt;......

打扫房间的各种方法 —— Java虚拟机的垃圾收集算法清单

“复制”算法也是标记哪些文件要删除,然后把不需要删除的文件,拷贝到另一个文件夹,接着在原来的文件,ctrl+A,右键-删除,一把全部删了。在Linux上就是 rm -rf *:

打扫房间的各种方法 —— Java虚拟机的垃圾收集算法清单

而“标记-整理”算法,也是先标记,接着把文件列表按照文件名排序,然后按住shift,选中从第一个“toDelete_”的文件,到最后一个文件之间的全部文件,然后右键-删除。在Linux上就是,rm -rf toDelete_*

打扫房间的各种方法 —— Java虚拟机的垃圾收集算法清单

这样就能理解为什么“标记-清理”会比其他两种算法慢了吧 :)


总结

这一讲讲解了虚拟机进行垃圾收集的几种常用算法,下一讲,我们就要看看,这几种算法,或者说方法论,是如何在各种垃圾收集器中具体实现的,同时我们也将一起了解JVM中,到底有哪些垃圾收集器。


参考资料