[ faɪnəlaɪz ]
工作原理
一旦垃圾收集器准备好释放对象占用的存储空间,首先调用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异常。
带来的隐患
由于Finalizer线程优先级相较于普通线程优先级要低,而根据Java的抢占式线程调度策略,优先级越低的线程,分配CPU的机会越少,因此当多线程创建重写finalize方法的对象时,Finalizer可能无法及时执行finalize方法,Finalizer线程回收对象的速度小于创建对象的速度时,会造成F-Queue越来越大,JVM内存无法及时释放,造成频繁的Young GC,然后是Full GC,乃至最终的OutOfMemoryError。
为什么不能显示直接调用finalize方法?
如前文所述,finalize方法在垃圾回收时一定会被执行,而如果在此之前显示执行的话,也就是说finalize会被执行两次以上,而在第一次资源已经被释放,那么在第二次释放资源时系统一定会报错,因此一般finalize方法的访问权限和父类保持一致,为protected。