java垃圾回收器在回收时首先要判断对象是否还存活,是否可回收。那么,如何判断一个对象是否可回收呢?本文就这一问题做一个简单的整理。
很容易想到的一个简单的实现方式是给对象添加一个引用计数器,每当有一个地方引用就加1,引用失效则减1;任何时刻计数器为零的对象就是不会再被使用的,内存自动回收时就可以对它进行回收了。在大部分情况下,这确实是一种实现简单且判断效率高的不错的算法,但是这并不是主流的java虚拟机管理内存的算法,其最主要的原因是这种算法很难解决对象之间相互循环引用的问题。
举个简单的例子:
/**
*
* @author 爱上双眼皮儿的猫
*
*/
public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];
public static void tsetGC() {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
上述例子中objA和objB相互引用,如果使用引用计数器算法来判断对象是否存活,这两个对象是永远不会回收的。但是运行上述代码,结果显示这两个对象虽然都存在引用却还是都被回收,这也说明虚拟机并不是利用引用计数器算法来判断对象是否存活。那么,虚拟机中用的是什么算法呢?
可达性分析算法
顾名思义,该算法通过一系列称为“GC Root” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Root 没有任何引用链相连接(用图论来说就是从根节点到这个对象节点不可达)时,则证明这个对象是不可用的。如下图所示,对象 5, 6, 7虽然互相有关联,但是它们到GC Root 是不可达的,所以它们将被判定为死亡对象,即可回收对象。
在java语言中,可作为GC ROOTS的对象有如下几种:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象;
每个方法执行的时候,虚拟机都会创建一个相应的栈帧(栈帧中包括操作数栈、局部变量表、运行时常量池的引用),栈帧中包含这在方法内部使用的所有对象的引用(当然还有其他的基本类型数据),当方法执行完后,该栈帧会从虚拟机栈中弹出,这样一来,临时创建的对象的引用也就不存在了,或者说没有任何GC ROOTS指向这些临时对象,这些对象在下一次GC时便会被回收掉。
2.方法区中静态属性引用的对象;
静态属性是该类型(class)的属性,不单独属于任何实例,因此该属性自然会作为GC ROOTS的对象。只要这个class存在,该引用指向的对象也会一直存在。class 满足以下条件也是可以被回收的:
a.堆中不存在该类的任何实例
b.加载该类的classloader已经被回收
c.该类的java.lang.Class对象没有在任何地方被引用,也就是说无法通过反射再带访问该类的信息
3.方法区中常量引用得到对象;
4.本地方法栈中JNI(即一般所说的Native方法)引用的对象。