Oct 16, 2016 ruochenxing gc jvm

常见垃圾回收算法

对象存活算法

在回收垃圾对象之前,垃圾收集器需要确认哪些对象“存活”和那些对象应该被回收

引用计数(Reference Counting)

引用计数是最古老的一种算法,在微软的COM组件技术,Adobe的ActionScript3中都有使用。实现就是:对于一个对象A,只要任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1;当一个对象的计数器值为0时,则对象A则不可能再被使用。

引用计数的缺点在于:

  • 无法处理循环引用的情况,例如A引用了B,B引用了A,但是系统中不存在任何第3个对象引用了A,B(此时A,B应该是要被回收的),但是A,B的计数器都为1,无法被回收。
  • 引用计数器要求每次引用产生或者消除时,都需要做相应的加减运算,会对系统性能产生影响。

可达性分析算法 (Reachability Analysis)

在主流的商用程序语言的主流实现中,都是通过可达性分析来判断一个对象是否应该存活的。其基本思想就是通过一系列的称为“GC Roots”的对象作为根节点,从这些节点开始向下搜索,搜索所走过的路径称为“引用链”。当一个对象到“GC Roots”没有任何的引用链相连时,则证明此对象可以被回收。

在Java虚拟机中,可作为GC Roots的对象包括以下几种:

  • 虚拟机栈(栈帧中局部变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的native方法)引用的对象

扩展:Java语言的四种引用类型:强引用,软引用,弱引用,虚引用

垃圾回收算法

标记清除算法(Mark-Sweep)

标记清除算法将垃圾回收分为标记清除两个阶段。在标记阶段,标记所有从根节点开始的可达对象,此时未标记的对象即为垃圾对象,等待回收。在清除阶段,清除所有的未标记对象。

标记清除的缺点在于:

  • 回收后的空间容易产生空间碎片

复制算法(Copying)

复制算法的核心思想是将原有的内存空间分为两块,每次只使用其中的一块,在垃圾回收时,将存活的对象复制到另一块未被使用的空间,之后清除原来使用的空间中的所有对象。

复制算法的优点:

  • 如果系统的垃圾对象多,复制算法要复制的存货对象就少,因此效率会高
  • 不会产生空间碎片

缺点:

  • 可用内存折半,总是存在一块内存不被使用

在Java虚拟机新生代串行垃圾回收器中,使用了复制算法的思想。新生代被分为eden,from,to三个内存空间。其中fromto是两块大小相当,地位相同且可进行角色互换的空间,也统称为survivor空间。

标记压缩算法 (Mark-Compact)

相比于复制算法存活对象少,垃圾对象多的情况,标记压缩算法通常用于大部分对象都是存活对象的情况(老年代的回收算法)。它在标记清除算法的基础上做了一些优化,在标记存活对象后,将所有的对象压缩到内存的一端,然后再清除边界外的所有空间。

优点:

  • 避免产生碎片
  • 内存使用率高,不存在内存折半的现象

因为只是比标记清除多了一个压缩的步骤,所以也被称为标记清除压缩算法(MarkSweepCompact)

分代算法 (Generational Collecting)

在上面的一系列算法中,并没有哪种算法可以完全的替代其他的算法。它们各自具备自己独特的优势和特点。因此,将内存区间根据对象的特点分成几块,每块使用不同的回收算法,以提高垃圾回收效率,这就是分代算法。

在Java虚拟机中,所有的新建对象都会优先存放在新生代内存空间中,对于新生代内存空间中的对象,很容易就被回收,因此新生代适合复制算法。当一个对象经过几次回收后仍然存活,该对象就会被放入老年代内存空间,在老年代内存空间的对象,经过几次垃圾回收后仍然存活的概率通常很大,根据分代的思想,老年代适合标记压缩或标记清除算法

Share this post

Search widget

Timeline

Friendly Links