文章目录
前言
世界观第一,体能第二,技术第三。
Obejct.finalize()方法
JDK 中 Object 类有一个空实现的方法 finalize()。但是你无法直接通过Object对象调用这个方法,只能使用子类调用。
覆盖重写该方法的目的是完成一些收尾的工作。
protected void finalize() throws Throwable { }
覆盖从写 finalize() 与 JVM 垃圾回收
HotSpot 虚拟机使用可达性分析法判断一个对象是否还被引用, 如果 GC Root 的引用链中没有对该对象的引用了,那么对象也不会立即被回收,而是需要判断该对象所属类是否覆盖重写了 finalize()。
如果没有覆盖重写 finalize()方法,那么该对象就可以被进行垃圾回收了。
如果覆盖重写了finalize()方法,那么就要判断该方法是否被调用过了,如果该方法已经被调用了,也会对该对象进行垃圾回收。如果该对象的 finalize()方法 还没有被调用,那么会将该对象放入java.lang.ref.Finalizer.ReferenceQueue队列中,然后由JVM的 后端驻留线程Finalizer依次调用队列中对象的finalize()方法。
如果覆盖重写 finalize()方法且进行了调用,调用过程没有导致该对象从新被 GC Root加入调用链,那么在下一次垃圾回收的时候该对象就会被回收。但是即使该对象被重新加入到 GC Root 调用链, finalize()也只会执行一次。
命令行 jstack 查看 JVM 后台驻留线程 Finalizer
使用 HotSpot 提供的命令行工具 jstack 我们可以查看到 JVM 中的后台驻留线程
Finalizer的相关信息。
该线程用于调用 java.lang.ref.ReferenceQueue 队列中对象的 finalize()方法
其优先级很低,所以并无法保证其运行的时效性。
“Finalizer” #3 daemon prio=8 os_prio=31 tid=0x00007fa36b855000 nid=0x5203 in Object.wait() [0x000070000c1bb000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000740f1fcf8> (a java.lang.ref.ReferenceQueueLock)atjava.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)−locked<0x0000000740f1fcf8>(ajava.lang.ref.ReferenceQueueLock)atjava.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)−locked<0x0000000740f1fcf8>(ajava.lang.ref.ReferenceQueueLock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
测试方法
子类覆盖重写 finalize()方法
手动调用 finalize()方法
手动触发垃圾回收 System.gc();
重复调用垃圾回收,根据打印日志判断 finalize()是否被调用一次,和对象是否被回收。
finalize() 引发内存溢出
这里推荐一篇很好的文章:http://it.deepinmind.com/gc/2014/05/13/debugging-to-understand-finalizer.html
大概的意思是,如果你覆盖重写了 finalize()方法,当系统在进行垃圾回收时会将该对象放入 java.lang.ref.ReferenceQueue 队列,等待后台驻留线程 Finalizer调用finalize()方法,又由于该线程优先级别很低,最后导致 java.lang.ref.ReferenceQueue 队列存储的内容越来越大,某种情况下导致内存溢出——因为垃圾回收并没有彻底完成。
回顾一下,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()方法 可能导致对象复活,但是至少会导致对象无法被立即回收。
由于垃圾回收的时机是我们无法掌控的,这就导致了内存溢出的风险,所以不建议自己覆盖重写这个方法,如果有什么需要特殊操作的,比如资源回收之类的,可以使用try···finally。
此外,jdk1.7支持 try(资源声明),不用明写 finally,后面自动回收资源了。
参考资料
[1]、《深入理解 Java 虚拟机》
[2]、http://it.deepinmind.com/gc/2014/05/13/debugging-to-understand-finalizer.html
本文深入解析Java中Object类的finalize()方法,探讨其与垃圾回收的关系,如何避免内存溢出风险,以及最佳实践建议。
1563

被折叠的 条评论
为什么被折叠?



