# 垃圾回收算法 Uncompleted

垃圾回收(Garbage Collection)是一种自动的内存管理机制。当动态内存不再需要时,就应当予以释放,以让出内存,这就是垃圾回收

垃圾回收算法的优缺点主要看以下几个方面:

  • 吞吐量
  • 最大暂停时间
  • 堆使用效率
  • 访问的局部性

# 引用计数算法 Reference Counting

记录每个对象被引用的次数。每次新建对象、赋值引用和删除引用的同时都要更新计数器,如果计数器的值为0就直接回收内存。

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值就加1;当引用失效时,计数器的值就减1。当计数器的值为0时,这个对象就是不可再被使用的。

主流Java虚拟机里并没有选择引用计数法来管理内存,主要原因是「它很难解决对象之间互相循环引用的问题」。

/**
 * 因为objA.instance = objB, objB.instance = objA;
 * 之后令objA = null, objB = null,这两个对象就不可能再被访问了
 * 但因为它们互相引用着对方,导致引用计数都不为0,导致无法通知GC收集器回收它们
 */
public static void testGC() {
    ReferenceCountingGC objA = new ReferenceCountingGC();
    ReferenceCountingGC objB = new ReferenceCountingGC();
    objA.instance = objB;
    objB.instance = objA;

    objA = null;
    objB = null;

    System.gc();
}

# 可达性分析算法 Reachability Analysis

这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径被称为引用链(Reference Chain),当一个对象到达GC Roots时都没有任何引用链与其相连时,就证明该对象是不可用的,将会被判定是可回收的对象。

# 标记-清除算法 Mark-Sweep GC

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

  1. 标记阶段:从根集合出发,将所有的活动对象及其子对象打上标记
  2. 清除阶段:遍历堆,将所有的非活动对象(未打上标记的对象)连接到空闲链表上

不足之处

  • 效率问题:标记和清除的两个过程效率都不高
  • 空间问题:标记清除之后会产生大量的不连续的内存碎片,空间碎片太多就会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

# 标记-压缩(整理)算法 Mark-Compact GC

后续步骤不是直接对可回收的对象进行清理,而是让所有存活的对象都向一端移动,即将所有的活动对象紧密地排在堆的一端(压缩),然后直接清理掉端边界以外的内存,消除了内存碎片,但压缩需要成本。

# 复制算法 Copying

将堆分为两个大小相等的From和To两块,每次只使用其中的一块。利用From空间进行分配,当From空间满了,就将其中的活动对象复制到To空间,之后将两个空间互换即完成GC。

每次都是对整个半区进行内存回收,分配内存时也就不用考虑内存碎片等复杂情况。

但这种算法的代价是将内存缩小到了原来的一半,因为分成了两块相等的内存空间。