垃圾回收策略与算法

垃圾收集器(garbage collector)是一种动态存储分配器,它自动释放程序不再需要的内存块,这些块也称为垃圾。自动回收垃圾的过程则称为垃圾收集(garbage collection)。在一个支持垃圾收集的语言中,程序显式地申请内存,但从不需要显式的释放它们。垃圾收集器会定期识别垃圾块,并将垃圾块放回空闲链表中。

垃圾回收策略

引用计数法

当对象被引用时程序计数器 +1,释放时 -1。当为 0 时证明对象未被引用,可以回收。

但是这个算法有明显的缺陷,对于循环引用的情况下,循环引用的对象就不会被回收。例如下图:对象 A与 B 循环引用,即便没有其他的对象引用 A 和 B,A 和 B 也都不会被回收。

可达性分析

通过一系列称之为 GC Roots 的对象作为起点,从此起点向下搜索,所走过的路径称之为引用链。当一个对象到 GC Roots 没有任何引用链相连接,代表此对象不可达。

在 Java 可以作为 GC Roots 的对象包括:

  1. 虚拟机栈(帧栈中的局部变量表)中的引用对象;
  2. 方法区中类静态属性(static)引用的对象;
  3. 方法区中常量(constant)引用的对象;
  4. 本地方法栈中 JNI(即一般说的 Native 方法)的引用对象;

垃圾回收算法

标记-清除算法(Mark-Sweep)

首先利用可达性去遍历内存,把存活对象和垃圾对象进行标记。标记结束后统一将所有标记的对象回收掉。

  • 标记阶段:标记出所有需要回收的对象;
  • 清除阶段:回收被标记的对象所占用的空间。

优点:实现简单

缺点:效率较低,并且会产生大量不连续的内存空间碎片,后续可能发生大对象不能找到可利用空间的问题

标记-整理算法(Mark-Compact)

标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。

复制算法(Copying)

按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。

优点:实现简单,运行高效,不易产生碎片

缺点:可用内存缩小为了原来的一半,浪费空间。若存活对象增多的话,该算法的效率会大大降低

分代收集算法(Generational Collection)

分代收集法是目前大部分 JVM 所采用的方法,GC 堆划分为新生代(Young Generation)和老生代(Tenured/Old Generation)。新生代和老年代的默认比例为 1 : 2;新生代又分为 Eden 区, from Survivor 区(简称 S0 ),to Survivor 区(简称 S1 ),三者的比例为 8: 1 : 1。

我们把新生代发生的 GC 称为 Young GC(也叫 Minor GC ),老年代发生的 GC 称为 Old GC(也称为 Full GC )。新老生代会使用不同的垃圾回收算法,一般情况下

  • 新生代使用复制算法
  • 老年代使用标记清除算法或者标记整理算法

在新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,使用复制算法比较合适,只需要付出少量存活对象的复制成本就可以完成收集。老年代对象存活率高,适合使用标记-清理或者标记-整理算法进行垃圾回收。

对象的内存分配主要在新生代的 Eden Space 和 Survivor 区的 From Space(Survivor 目前存放对象的那一块),少数情况会直接分配到老生代。当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后,Eden Space 和 From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 From Space 进行清理。如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。在进行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反复循环。当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。默认情况下年龄到达 15 的对象会被移到老生代中。

参考资料