工作原理
一旦垃圾收集器准备好释放对象占用的存储空间,首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存。
finalize()方法的通用格式如下:
Java允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。Java语言规范中不仅不保证终结方法会被及时地执行,而且根本不保证他们会被执行。
如果使用了终结方法,就记住一定要调用super.finalize()。记住一句话:避免使用终结方法–finalize()。可以在finalize()让这个对象再次被引用,避免被GC回收;但是最常用的目的还是做cleanup
finalize工作流程
回顾一下,Finalizable对象的生命周期和普通对象的行为是完全不同的,列举如下:
JVM创建Finalizable对象
JVM创建 java.lang.ref.Finalizer实例,指向刚创建的对象。
java.lang.ref.Finalizer类持有新创建的java.lang.ref.Finalizer的实例。这使得下一次新生代GC无法回收这些对象。
新生代GC无法清空Eden区,因此会将这些对象移到Survivor区或者老生代。
垃圾回收器发现这些对象实现了finalize()方法。因为会把它们添加到java.lang.ref.Finalizer.ReferenceQueue队列中。
Finalizer线程会处理这个队列,将里面的对象逐个弹出,并调用它们的finalize()方法。
finalize()方法调用完后,Finalizer线程会将引用从Finalizer类中去掉,因此在下一轮GC中,这些对象就可以被回收了。
Finalizer线程会和我们的主线程进行竞争,不过由于它的优先级较低,获取到的CPU时间较少,因此它永远也赶不上主线程的步伐。
程序消耗了所有的可用资源,最后抛出OutOfMemoryError异常。
为什么不能显示直接调用finalize方法?
finalize方法在垃圾回收时一定会被执行,而如果在此之前显示执行的话,也就是说finalize会被执行两次以上,而在第一次资源已经被释放,那么在第二次释放资源时系统一定会报错,因此一般finalize方法的访问权限和父类保持一致,为protected。
Java中为什么在重写finalize()方法时首选调用super.finalize()?
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("一个 Cat 被销毁");
}
super是用在子类中,目的是访问直接父类中被屏蔽的成员。
调用super.finalize(),父类中finalize如果有关闭资源的逻辑,当子类中finalize没有显示调用super.finalize()时父类的finalize不会被执行,所以父类的资源无法关闭。
带来的隐患
由于Finalizer线程优先级相较于普通线程优先级要低,而根据Java的抢占式线程调度策略,优先级越低的线程,分配CPU的机会越少,因此当多线程创建重写finalize方法的对象时,Finalizer可能无法及时执行finalize方法,Finalizer线程回收对象的速度小于创建对象的速度时,会造成F-Queue越来越大,JVM内存无法及时释放,造成频繁的Young GC,然后是Full GC,乃至最终的OutOfMemoryError。
@Override的作用
@Override是伪代码,表示重写。(不写@Override也可以)
写上的好处:
1、可以当注释用,方便阅读;
2、编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错。例如,你如果没写@Override,而你下面的方法名又写错了,这时你的编译器是可以编译通过的,因为编译器以为这个方法是你的子类中自己增加的方法。