- 上一篇说过了,目前主流的JVM使用的是可达性分析法,通过扫描JVM指定的某些对象作为根,扫描出整个内存中所有与之关联的对象,没有关联的对象则GC认为可以清理。
- 但其实,在通过可达性分析法中不可达的对象,也并不是一定会被清除,当可达性分析后,会对没有被跟引用的对象进行第一次标记,并且执行一次筛选,筛选的条件就是对象是否有必要执行的finalize方法。对对象没有覆盖finalize方法或者finalize方法已经被虚拟机调用过。JVM会将这两种情况视为没有必要执行的finalize方法。
- 如果这个对象被判定为必要执行时finlize方法时,那么这个对象会被设置到一个叫做F-Queue队列中,并在一个稍后由虚拟机自动创建的低优先级的Finlizer线程去执行它,执行其实是指JVM会去触发这个方法,并不会等待执行完毕,这样做是为了,如果一个对象的finalize方法执行缓慢,或者发生死循环,将会导致F-Queue一直处于等待状态,甚至导致整个内存回收系统崩溃。
- finlize方法是帮助对象逃脱死亡的最后一次机会,稍后GC会在F-Queue中对所有对象进行第二次小规模的标记,如果在finalize中拯救了自己(重新和引用链上的对象进行关联),例如将this赋值给某个对象的成员变量,那么第二次标记的时候,它将会被移除即将回收的集合;但是如果这个时候这个对象还没有逃脱的话,那么基本上等待它的就真的是死亡了。
public class GcObj {
public static GcObj SAVE_HOOK = null;
@Override
protected void finalize() throws Throwable {
//第一次清理时虚拟机会执行这个方法,但是对于一个对象虚拟机只会执行一次这个方法
super.finalize();
System.out.println("清理方法运行了");
SAVE_HOOK = this;
}
public void isAlive() {
System.out.println("我还活着");
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new GcObj();
//第一次拯救自己
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("Dead");
}
System.out.println("第一次拯救结束");
//第二次拯救自己 失败
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("Dead");
}
}
}
- 这串代码中,首先GcObj对象通过重写了Object的finalize方法,这个方法中通过将This赋值给一个static对象,试图拯救自己17行到27行是第一次拯救的代码,执行GC后,因为SAVE_HOOK对象为null,那么创建的GcObj对象则会被GC标记,因为Finalizer线程优先级比较低,所以通过Sleep让main线程等待一下,之后再运行一次同样的代码,看如下的执行结果可知finalize确实被执行了一次,并且成功拯救了这个对象,第二次同样的操作缺无法拯救这个对象了
清理方法运行了
我还活着
第一次拯救结束
Dead
- 需要特别注意的是,之前很多人用finalize方法来视图调用关闭资源的方法,但是由于finalize方法运行代价高昂,需要多等待一次GC,不确定性大,无法保证调用顺序,这是不对的。如果需要关闭资源的话try finally代码块足够我们使用了。这个方法只需要知道存在以及什么时候调用即可,正常使用中不建议使用