在进行垃圾回收的时候,我们需要完成三件事情。

  • 哪些内存需要回收
  • 什么时候回收
  • 如何回收

如何判断对象已死

判断对象已死有如下几种方式:

  • 引用计数法
  • 根搜索算法

引用计数法

引用计数法的大致思想是,给对象中添加一个引用计数器,每当有一个地方引用它,计算器就加 1.当引用失效,计数器减 1。但是这个方法不能解决循环引用的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ReferenceCountGc
{
public Object instance;

public static void main(String[] args)
{
ReferenceCountGc object1 = new ReferenceCountGc();
ReferenceCountGc object2 = new ReferenceCountGc();

object1.instance = object2;
object2.instance = object1;

object1 = null;
object2 = null;

}
}

什么的代码是循环引用,当这2个变量不需要的时候也是会被垃圾回收的。所以 jvm 不是采用这种垃圾回收机制。

根搜索算法

这个算法是通过 “GC Roots” 对象作为起始点,从这些节点开始搜索,搜索走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连,则证明此对象是不可用的。

在 java 当中,作为 GC Roots 的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
  • 方法区中的静态类属性引用的对象。
  • 方法区中的常量引用的对象。
  • 本地方法栈中 JNI 的引用的对象。

gc-roots

在根搜索算法中不可达的对象,也并非是一定死掉的,这个时候它们暂时处于第一次标记,一个对象的死亡,至少要经历两次标记过程。

如果对象在进行根搜索后发现没有与 GC Roots 相连接的引用链,那么它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize() 方法。

当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行。

垃圾收集算法

介绍一些常见的垃圾收集算法。

标记-清除算法

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

这个算法有两个缺点:

  • 效率不高,标记和清除的效率都不高
  • 清除后会产生大量不连续的内存碎片

详细算法描述可见这里

复制算法

这个算法是将可用内存分成相等两块,每次只使用其中的一块。当这块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

这个算法是比较高效的,但是将内存缩小为原来的一般,带价有点高。

一般是将内存控件分成一块较大的 Eden 空间和两块较小的 Survivor 空间。HotSpot 虚拟机默认 Eden 和 Survivor 的大小比例是 8:1,也就是说每次新生代中可用内存空间为整个内存的 90%(80%+10%),只有 10% 的内存会被浪费。

标记整理算法

这个算法是先标记可以清除的对象,然后让所有存活的对象都向一段移动,然后直接清理掉端边界以外的内存。

分代收集算法

一般是把 java 堆分为新生代和老年代,这样在新生代中因为每次垃圾收集都会发现大批对象死去只有少量存活,所以使用复制算法,在老年代中因为对象存活率比较高,所以使用标记清理或者标记整理算法。

【参考资料】

  1. 深入理解Java虚拟机

—EOF—