首页 > Java > Java GC之垃圾回收算法

Java GC之垃圾回收算法

上一篇文章简单写了一下JVM如何判断一个对象是否已经死了,当判断出一个对象已经死了之后,接下来就要进行垃圾回收了,所以在进行垃圾回收之前,先让我们看看垃圾回收的算法有哪些。

1. 标记-清除算法

标记清除见名知意该算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收所有被标记的对象,至于如何标记就是上一篇文章中所讲的方法了。这种算法比较简单,容易理解,同时也是一个最基础的垃圾回收算法,后面所讲的算法都是对他的改进,至于为什么需要改进,因为他主要存在两个不足:

①. 效率问题,标记和清除两个阶段的效率都不高;
②. 空间问题,标记清除之后会留下大量的不连续的内存碎片,内存碎片会导致当后面在程序的运行过程中可能需要给较大的对象分配空间时,无法找到足够的内存而不得不提前触发另一次垃圾回收。这种算法的执行过程如下图:

mark-sweep

2. 复制算法

复制算法为了解决标记清除算法的效率问题:它将内存分为容量大小相同的两块,每次只使用其中一块,当这一块的内存空间用完了,他就讲里面存活着的对象复制到另一块上面,然后再把已使用过的这一块内存空间一次性清理掉,这样使得每次都是对半个内存区域进行会回收,内存分配时也不用考虑空间碎片的问题,只需要移动指针,按顺序分配即可,实现简单,运行高效。但是他也有缺点:每次使用的内存空间只有整个空间的一半,这“浪费”有点高啊。复制算法的执行过程如下图:

copying

不过现代的商业JVM都采用了这种算法来回收新生代,究其原因不仅仅他解决了效率问题,更是经研究表明:新生代中的对象高达98%都是“朝生夕死”的,所以这样一来就不需要1:1来划分空间了。直接将内存空间分为一块较大的Eden区和两块较小的Survivor区,每次使用Eden和其中一块Survivor区,当需要回收时,直接将Eden和Survivor中还存活的对象一次性的复制到另一块Survivor区,最后在清理Eden和刚使用过的Survivor。HotSpot虚拟机Eden和Survivor的大小比例默认为8:1,也就是每次使用的空间大小是90%,只“浪费”了10%。但是98%的对象回收也不能保证,每次存活的对象所使用的空间小于10%,所以当Survivor空间不够时,就需要其他空间(一般是老年代)进行分配担保,如果另一块Survivor空间没有足够的空间存放上一次新生代垃圾回收存活的对象时,这些对象将直接通过分配担保机制进入老年代。

3. 标记-整理算法

复制算法适用于新生代的对象“朝生夕死”,如果一个区域内的对象老是不死,不仅内次都需要复制大量的对象,效率很低,而且还需要额外的空间进行担保(分配担保是一个很复杂的东西,今后有机会会说到),所以对于老年代的对象,这种算法是不适合的,于是就提出了标记-整理算法。
标记-整理算法和标记-清除算法一样,也是分两个阶段,而且第一个阶段也一样,都是标记,所不同的是第二个阶段,不是对可回收的对象进行直接清理,而是让还存活着的对象向一端移动,然后直接清理掉端边界以为的内存空间,该算法的执行过程如下图:

mark-compact

4. 分代收集算法

当前的商业JVM都是采用的这种算法,其实这种算法并没有什么新思想,而是根据对象存活周期的不同将内存划分几块,一般是把Java堆分为两块:新生代和老年代,这样就可以根据各个代的不同特点采取最适当的垃圾收集算法。新生代的对象大多都是“朝生夕死”的,而且还可以有老年代进行担保,那就采用复制算法,只需要付出少量的存活对象的复制成本就可以完成收集,而且一般也不需要启用担保策略,而老年代的对象存活率一般比较高、没有空间进行担保,就只有采用“标记-清理”或者“标记-整理”算法来进行回收了。

参考资料:周志明《深入理解Java虚拟机》第二版第三章

分类: Java 标签: ,
  1. 本文目前尚无任何评论.

请输入正确的验证码