谁才是真正的垃圾:判断对象的可触及性
垃圾回收的基本思想是考察每一个对象的可触及性,即从根节点开始是否可以访问到这个对象,如果可以,则说明当前对象正在被使用,如果从所有的根节点都无法访问到某个对象,说明该对象已经不再使用了,一般来说,此对象符合垃圾回收的条件。但是,一个无法触及的对象有可能在某个条件下复活自己,如果这样,那么对它的回收就是不合理的,为此,需要给出一个可触及性状态的定义,并规定在什么状态下,才可以安全的回收对象。
可触及的包括3种状态:
- 可触及的:从根节点开始,可以到达这个对象;
- 可复活的:对象的所有引用都被释放,但是对象有可能在finalize()函数中复活;
- 不可触及的:对象的finalize()函数被调用,并且没有复活,那么就会进入不可触及状态,不可触及的对象不可能被复活,因为finalize()函数只会被调用一次。
以上3种状态,只有在对象不可触及时才可以被回收。
1 对象的复活
实例1 :前面提到,对象可能在finalize()函数中复活自己,这里给出一个实例。
package com.jvm;
public class CanReliveObj {
public static CanReliveObj obj ;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called");
obj = this;
}
@Override
public String toString() {
return "CanReliveObj";
}
public static void main(String[] args) throws InterruptedException {
obj = new CanReliveObj();
obj = null;
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj is null");
}else{
System.out.println("obj 可用");
}
System.out.println("第二次GC");
obj = null;
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj is null");
}else{
System.out.println("obj 可用");
}
}
}
运行代码打印:
CanReliveObj finalize called
obj 可用
第二次GC
obj is null
可以看出,在第一次将obj置为null后,进行GC,结果发现obj对象被复活了。等到第二次再释放对象引用并运行GC,对象才真正被回收。这是因为第一次GC时,在finalize()函数调用之前,虽然系统中的引用已经被清除,但是在实例方法finalize()中,对象的引用this依然被传入方法内部,导致引用外泄,对象复活,此时对象又变成可触及状态。由于finalize()函数只会被调用一次,因此,在第二次清除释放对象的引用时,对象就再无机会复活,因此会被回收。
注意:
finalize()函数是一个糟糕的应用模式,不推荐使用finalize()函数释放资源。
因为:其一,finalize()函数有可能发生引用外泄,在无意间复活对象
其二,由于finalize()是被系统调用的,调用时间是不明确的,因此不是一个好的资源释放方案,推荐使用try-catch-finally语句进行资源的释放。