本篇文章,在学习JVM的过程中,根据书籍(深入理解Java虚拟机)、网络和个人的一些想法总结而成。
你有我有全都有,风风火火闯九州!
你懂我懂咱都懂,风风火火搞定它!
垃圾收集算法到现在(JDK8)有3种,“标记-清除”算法、“标记-复制”算法、“标记-整理”算法。
发展史以及各个算法诞生时要解决的问题,如下图所示。
为了更形象的说明这三种算法的区别和含义,举个例子。
割韭菜怎么割比较好,是个技术活:
图片来自百度。
韭菜地是各种各样的,我们如何才能长的更好?考验技术的时候到了。
一、算法核心思想介绍
1、标记-清除算法
核心思想:
标记-清除算法就像它的名字一样,算法分为“标记”和“清除”两个阶段,先对可回收的对象进行统一标记,在标记完成后再进行统一的回收调被标记的对象。
也可以反过来,先统一标记存活的对象,然后统一回收未被标记的对象。
标记过程就是对对象进行判定是否可回收,是否属于垃圾的过程。
图片演示:
假如我们有如下内存,我们对其进行使用。
使用后的结果如下:
第一步:首先把内存中所有的对象进行检查一遍,是垃圾(可回收)的对象就进行标记。如下图所示,标记成灰色。
第2步:将标记好的对象,也就是灰色区域的清理掉。
回收前VS回收后
可以看到,清理完成后空闲的空间并没有挪动,所以“标记-清理”算法在清理后,整个空间的位置是非常零散的(也就是经常所说的内存碎片,不连续的空间)。
这也就是“标记-清理”算法的不足之处。
如果此时来了一个比较占用大内存的对象,而恰好腾出来的空间装不下来,反而如果把不连续的空间组合到一起就满足了。此时就会抛OOM异常(内存溢出)。
它的主要缺点有两个:
- 1、执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,此时就会进行大量标记和清除的动作。导致标记
2、标记-复制算法
标记-复制算法常被简称为复制算法。为了解决标记-清除算法面对大量可回收对象时执行效率低的问题。1969年Fenichel提出了一种称为“半区复制”(Semispace Copying)的垃圾收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。如果内存中多数对象都是存活的,这种算法将会产生大量的内存间复制的开销,但对于多数对象都是可回收的情况,算法需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可。这样实现简单,运行高效,不过其缺陷也显而易见,这种复制回收算法的代价是将可用内存缩小为了原来的一半,空间浪费未免太多了一点。
简单点说:
标记-复制算法,是为了解决标记-清除算法面对大量可回收对象时执行效率低的问题。它将可用内存按容量划分为大小相等的两块区域,每次只使用其中一块。当这一块的内存用完了,就将还活着的对象复制到另外一块上,然后再把已使用过的内存空间清理掉。
垃圾回收前的状态:
垃圾回收后的状态:
会把另一半中存活的对象,复制到另外一边,然后清除。
如果下次再做垃圾回收的时候,又会复制到另一边,以此类推。
对比图:
3、标记-整理算法
标记-复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
针对老年代对象的存亡特征,1974年Edward Lueders提出了另外一种有针对性的“标记-整理”(Mark-Compact)算法,其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存,“标记-整理”算法的如下图所示。
标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。
二、总结
为了更好记住,抛出一个问题,解答一个问题的形式来说明吧。
面试官问:
你了解哪些GC算法,有什么区别,能分别说一下吗?
答:
我们常见的有三个,分别为“标记-清除”算法、“标记-复制”算法、“标记-整理”算法。
其中标记-清除算法是基础,标记-复制算法和标记-整理算法可以说是在标记-清除算法的基础上得以改进不足从而得到的算法。
- 标记-清除算法就像它的名字一样,分为标记和清除两个阶段。首先对内存中可回收的垃圾对象进行标记,然后统一进行清理。这个标记的过程其实就是判断是否为可回收的垃圾。不足之处就是随着对象的增多,此算法就会消耗很多的时间去完成相应的操作。
- 标记-复制算法其实就是为了解决标记清除算法效率低的问题。它把内存分为平等的两份空间,每次只使用一份,当触发到垃圾回收时机的时候,会把当前正在使用的这份内存空间中存活的对象,复制到另外一份内存空间,而且是连续的。然后清理刚才复制前的另一空间。不足之处就是如果使用内存达到了50%的情况,就会当作100%的情况,必须借助另外的内存才可以满足,很浪费内存空间。
- 标记-整理算法是为了解决标记-复制算法内存浪费的问题。标记-整理算法和标记-清理算法差不多,不同之处是,当触发垃圾回收机制的时候,此算法会把存活的对象移动到另一端的内存空间,然后清理掉边界以外的内存空间,而且整理后的内存空间是连续的。