深入理解Java虚拟机(三)垃圾回收算法


  在我们知道了如何判断对象已“死”? 之后我们就要去回收内存空间,垃圾回收算法主要有一下几种:

  1. 标记-清除算法
  2. 复制算法
  3. 标记整理算法
  4. 分代收集算法

1. 标记-清除算法

  “标记-清除”算法是最基础的收集算法。算法分为 “标记”“清除” 两个阶段:

  1. 标记: 遍历堆,标记出所有需要回收的对象(此处采用的是可达性分析算法进行标记)
  2. 清除: 标记结束后,再次遍历堆,统一回收所以被标记的对象。

深入理解Java虚拟机(三)垃圾回收算法

通过上述过程我们可以感觉到“标记-清除”法的不足:

  1. 效率问题:标记、清除分为两次进行且每次都要遍历全部空间,可以发现标记和清除这两个过程的效率都不高。
  2. 空间问题:标记清除后我们会产生大量不连续的内存碎片,空间碎片太多可能会导致后面在程序运行中需要分配较大对象时,无法找到足够连续内存而不能不提前触发另一次垃圾回收。

可能第二个不足,通过上面的图解不是很清楚,我们再来画一个图:
深入理解Java虚拟机(三)垃圾回收算法
通过上面图,我们可以发现使用的空间都没有集中起来,导致该算法进行中出现的效率和空间问题。

2. 复制算法(新生代回收算法)

“复制”算法是为了解决“标记-清除”的效率问题。
算法概述:
  复制算法将可用内存按容量划分为大小相等的两块,每次只是用其中的一块。当这块内存需要垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。
深入理解Java虚拟机(三)垃圾回收算法
优点: 该算法的好处时,每次都是对整个搬去进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。该算法实现相对“标记-清除”简单,运行高效。
现在商用的虚拟机(包括HotSpot都是采用这种收集算法来回收新生代)。

在新生代使用复制收集算法的原因: 新生代中98%的对象都是“朝生夕死”的,所以复制的对象很少,效率较高。
  新生代使用了复制收集算法,那么它的空间是怎么划分的,由于上面有说“新生代中98%的对象都是朝生夕死的”,所以不需要按照1:1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden空间和两块较小的Survivor空间(两个Survivor区域一个称为Fro’m区,另一个称为To区域),每次使用Eden和其中一块Survivor空间。当回收时将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。
  当Survivor空间不够用时,需要依赖其他内存空间(老年代)进行分配担保。
内存空间分配如下图所示:
深入理解Java虚拟机(三)垃圾回收算法
注意: HotSpot默认的Eden和Survivor的大小比例时8:1,也就是说Eden:Survivor From:Survivor To = 8:1:1。所以每次新生代可用内存空间为整个新生代容量的90%,而剩下的10%用来存放回收后的存活对象。
  了解了复制算法的思想,也知道了HotSpot的内存分配,下面我们来看一下HotSpot实现复制算法的流程:

  1. 当Eden区满的时候,会触发第一次Minor gc,把或者的对象拷贝到Survivor From区;当Eden区再次触发Minor gc的时候,这次会扫描Eden区和From区,对这两个区域进行垃圾回收,经过这次回收还存活的的对象,则直接复制到To区域,并将Eden和From区域清空。
  2. 当后续Eden又发生Minor gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。
  3. 部分对象会在From和To区域中来回复制,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果对象还是存活,就存入老年代。

深入理解Java虚拟机(三)垃圾回收算法

3. 标记-整理算法(老年代回收算法)

复制收集算法再对象存活率较高时会进行比较多的复制操作,效率会变低。因此老年代一般不能使用复制算法。针对老年代的特点,提出了一种“标记-整理”算法。标记过程仍与“标记-清除”算法过程一致,但后续步骤不是直接堆可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边缘以外的内存。
深入理解Java虚拟机(三)垃圾回收算法

4. 分代收集算法

  当前JVM垃圾收集都采用的是“分代收集”算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。
  一般是把Java堆分为新生代和老年代。

  • 新生代中,每次垃圾回收都有大批量对象死去,只有少量存活因此我们采用复制算法
  • 老年代中,***对象的存活效率高、没有额外的空间对它进行分配担保,***就必须采用“标记-清理”或“标记-整理”算法。

5. Minor gc 和 Full gc

在上面我们不停的在说Minor gc,那么他到底是什么?还有一种Full gc,这两种GC有什么不一样吗?

  • Minor gc(新生代gc):指发生在新生代的垃圾收集因为Java对象大多都具备朝生夕灭的特点,因此Minor gc(采用复制算法)非常频繁,一般回收速度也比较快
  • Full gc(老年代gc 或者 Major gc):指发生在老年代的垃圾收集出现了Major gc,经常伴随至少一次的Minor gc并非绝对,在Paraellel Scavenge收集器中就有直接进行Full gc的策略选择过程)。Major gc的速度会比一般的Minor gc慢10倍以上。