JVM在进行垃圾回收前,需要判断这个对象是否还在使用,哪些是不可能再被任何途径使用的,而主要的判断方法有以下两种

引用计数法

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。

这种方法的优点是原理比较简单,判定效率也高,但实际情况是想要用这种方法,需要做很多额外的工作,因为单靠引用计数法很难解决对象互相引用的问题。比如下面代码:

1
2
3
4
5
6
7
8
9
10
class A {
Object instance;
}

class B {
Object instance;
}

objA.instance = objB;
objB.instance = objA;

尽管他们两个都不会再被使用,但是因为他们两个互相引用着对方,导致计数器的值不为0。

而实际上,很少有虚拟机会采用这种方法。

可达性分析算法

这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

以下几种固定可作为GC Root的对象:

1、在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。

2、在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。

3、在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。

4、在本地方法栈中JNI(即通常所说的Native方法)引用的对象。

5、Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

6、所有被同步锁(synchronized关键字)持有的对象。

7、反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

除了上述内容外,还有其他对象临时性的加入,比如分代收集和局部回收(Partial GC)

一个对象的自救

在可达性分析算法中,被标记为不可达的对象并不一定会被清除。一个对象被清除前会经历两次标记,第一次是可达性分析算法执行后,发现该对象没有引用链到GC Roots,这时候会被第一次标记。

随后会进行一次筛选,看看次对象是否有必要执行finalize()方法。如果对象没有覆盖finalize()方法或者已经被调用过,则会被视为没有必要。

如果被视为没必要,那么该对象会被放入一个F-Queue的队列之中,之后会有一个由虚拟机创建的,地调度优先级的线程取执行他们的finalize()方法。这里虚拟机只会执行,但是不会等待执行结束。因为如果一个对象在执行时发生了死循环,那么会导致队列中其他对象无法执行,甚至导致整个系统崩溃。

如果对象在执行finalize方法时,只要重新与引用链上的任何一个对象建立关联即可。否则将会被虚拟机二次标记。被第二次标记的则会被清除。

回收方法区

方法区的垃圾收集主要回收两部分内容:废弃的常量不再使用的类型(这里指的是一个类的class对象)

废弃常量的判定比较容易,与Java堆中对象的判定比较相似。

但是判断一个类型是否属于不再使用的类条件比较苛刻,需要满足以下三点:

1、该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。

2、加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。

3、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

参考

《深入理解Java虚拟机》