1 如何判断对象为“垃圾”
在垃圾回收之前,首先要判断哪些对象为“垃圾”,也就是判断哪些对象已经死去了,这里面的死去的概念就是这个对象不可能再被任何途径使用。
1.1 引用计数器法
一个很简单、效率也很高的方法。给对象添加一个引用计数器,每当有一个地方引用它时,计数器+1;当引用失效时,计数器-1;任何时刻计算器值为0的对象即为不再被使用的对象。但是Java虚拟机没有使用这种方式,因为它无法解决循环引用的问题。事实上,当程序中存在循环引用的时候,JVM依然有很好的收集效果。所以JVM并没有采用这种方法
1.2 可达性分析法
目前Java、C#均采用的主流分析法。基本思想就是通过一系列的称为”GC ROOTs”(set集合)的对象作为起始点,从这些节点开始向下搜索,搜索过的路径称为引用链。当一个对象没有任何引用链相连的时候,则证明对象是不可用的。
图中, Object5、Object6、Object7是GCROOT的不可达对象 他们可能会被回收。
在Java中,可作为GCROOT的对象包括以下几种:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
-
本地方法栈中引用的对象
2 引用的概念
在Java1.2之前,对于引用的定义很简单:如果reference类型的数据中保存的是另外一个对象的地址,则这块内存代表一个引用。这种方式不能很好的说明对象的重要性,有时我们希望在系统资源不足的时候,回收一些对象,而当系统资源充足的时候,不对这些对象进行管理。于是在1.2之后,出现了关于引用的四个概念
- 强引用:代码中普遍存在的 如“Objectobject=new Object()“这种形式,这类称为强引用。只要强引用还在,垃圾收集器永远不会进行收集
- 软引用:用来描述一些还有用的非必需的对象。对于这一类对象,只有在系统发生内存溢出前引发的回收,才会把这些类对象纳入到回收范围之内。如果这些回收还没有足够的内存,则会发生内存溢出。
- 弱引用:描述非必须对象,强度比软引用还要弱一点。这一些对象只能生存到下一次回收前,只要垃圾收集器工作,他们就会被回收
- 虚引用:一个对象的虚引用几乎没有意义,也不能通过虚引用去获得一个对象。为对象设置虚引用的唯一目的就是在这个对象被回收的时候收到一个系统通知。
3 对象的死亡判定
3.1 两次标记判定死亡
对象经过可达性分析以后,还不能立即判定为死亡对象,要宣告一个对象死亡,至少需要经过两次标记过程。
(1) 如果对象经过可达性分析,发现它已经不与任何引用链相连接,那么它会被第一次标记并且进行筛选,筛选的条件是对这个对象是否有必要执行finalize方法。如果对象没有覆盖finalize方法或者已经被虚拟机执行过finalize方法,就称为没有必要。
(2) 如果有必要执行,对象就会被放进一个F-QUEUE的队列之中,并在稍后由虚拟机创建一个优先级较低的线程去执行,也就是调用finalize方法。但是不会等待它结束,因为如果finalize方法执行缓慢或者发生死循环,将会把这个执行线程挂起,这将会导致虚拟机的回收系统异常。
3.2 对象的自救复活
从上面可以知道,对象如果在finalize方法中把自己与某个引用链关联起来,就可以进行“自救”,即把自己移除出“即将回收”的集合中。如果这个对象还没有逃脱,那么它将被回收了。
public class StillAlive
{
static StillAlive hook=null;
@Override
protected void finalize() throws Throwable
{
super.finalize();
System.out.println("finalize invoked" );
//自救
hook=this;
}
public void isAlive()
{
System.out.println("i am still alive");
}
public static void main(String[] args) throws Throwable
{
hook=new StillAlive();
hook=null;
//第一次收集 自救成功 finalize 方法调用
System.gc();
Thread.sleep(500);
if(hook!=null)
{
hook.isAlive();
}
else {
System.out.println("i am dead");
}
hook=null;
//第二次收集 自救失败 finalize没有调用
System.gc();
Thread.sleep(500);
if(hook!=null)
{
hook.isAlive();
}
else {
System.out.println("i am dead");
}
}
}
从上面代码可以看出,对象的finalize方法确实被GC收集器触发过,而且仅仅触发了一次。并且在这一次触发的时候,成功逃脱了。然后第二次却失败了,这是因为系统仅仅会调用finalize方法一次,如果下一次面临回收的时候,是不会重复执行finalize方法的。