在java虚拟机运行时,一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法(如果定义了),并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以我们可以利用finalize(),在垃圾回收的时刻做一些清理工作。
而finalize()并不像C++中的析构函数一样,对象一定会被销毁。对于没有用的对象何时被销毁,是由虚拟机中的GC机制决定的。
此外,java虚拟机只知道释放那些由new分配的内存,所以finalize()还可以来清楚那些特殊的被分配的内存。这些不是由new分配的内存是如何产生的呢?这种情况主要发生在使用“本地方法”(native method)的时候。本地方法其实并不是由java编写的代码,是由C和C++编写的,而C和C++还能调用其他的语言,所以实际上可以调用任何代码。这样可以使java通过native方法实现更加底层的操作。C会调用malloc()之类的函数去分配内存,所以在finalize()中,应该调用free()去释放这些内存,而不要去指望GC了。
最后解释一下第一段中描述的,为什么要等到下次垃圾回收时才能真正回收内存。
首先,要搞清楚对象什么时候会被定义成需要清除
1.引用计数法
每个对象都含有一个引用计数器,当有引用连接至对象的时候,引用计数加1;当引用离开作用域或被置为了null时,引用计数减1,当某个对象的引用计数为0时,就释放其占用的空间。这种方式,简单快速,但存在着一个不好解决的问题,即几个都应该被回收的对象,相互循环引用,则引用计数永远都不会变为0。
2.根搜索算法
java虚拟机采用的是此算法。此算法的基本思路就是通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可以达)时,则证明此对象是不可用的。
图中object5,object6,object7他们虽然相互关联,但连接不到GC Roots,所以他们需要被回收。
在java中,有4种可以作为GC Roots 的对象:
1.虚拟机栈(栈帧中的本地变量表)中的引用的对象
2.方法区中的类静态属性引用的对象
3.方法区中的常量引用的对象
4.本地方法栈中JNI(即一般说的Native方法)
搞清楚了什么样的对象需要回收,那来看看GC后,这些对象是不是马上被回收
其实并不是,如果对象覆盖了finalize(),被回收至少要经历两个标记过程,中途可以得到“拯救”。
当第一次发生GC后,会先调用finalize()(前提覆盖了此方法),将此对象标记成需要回收,被放到了一个名叫F—Queue的队列中。之后如果此对象,重新能与引用链上的任何一个对象建立关联,则在第二次标记时,它将被移出“即将回收”的集合。如果没有“逃脱”,那么就离死不远啦。另外,finalize()只能被执行一次。
class FinalizeEscapeGC{
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive(){
System.out.println("yes,i am still alive");
}
protected void finalize() throws Throwable{
super.finalize();
System.out.println("finalize method executed");
FinalizeEscapeGC.SAVE_HOOK = this;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
FinalizeEscapeGC.SAVE_HOOK = new FinalizeEscapeGC();
FinalizeEscapeGC.SAVE_HOOK = null;
System.gc();
Thread.sleep(50);
if(FinalizeEscapeGC.SAVE_HOOK != null){
FinalizeEscapeGC.SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead");
}
//下面方法完全一样,但自救失败,因为finalize方法只能执行一次
FinalizeEscapeGC.SAVE_HOOK = null;
System.gc();
Thread.sleep(50);
if(FinalizeEscapeGC.SAVE_HOOK != null){
FinalizeEscapeGC.SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead");
}
}
}
运行结果:
目前finalize()能做的所有工作,使用try-finally能做得更好、更及时。