Java性能优化[04] - 关于finalize函数

本文摘抄自编程随想的博客

​ 上次的帖子聊了垃圾回收器的调优,当时啰嗦了比较长的篇幅,就没再继续提 finalize 的事儿(其实这玩意儿和 GC 是沾点儿边的)。今天咱就把 finalize 函数相关的性能话题拿来说一下。

finalize 函数的调用机制

  俺经常啰嗦:“了解本质机制的重要性”。所以今天也得先谈谈 finalize 函数的调用机制。在聊之前,先声明一下:Java虚拟机规范(见这里),并没有硬性规定垃圾回收该不该搞,以及该如何搞。所以俺这里提到的 finalize 函数的调用机制,或许适用于大多数 JVM,但【不保证】适用于所有的 JVM。

何时被调用?

  finalize 啥时候才会被调用捏?一般来说,要等到JVM开始进行垃圾回收的时候,它才【有可能】被调用。而 JVM 进行垃圾回收的时间点是【非常】不确定的,依赖于各种运行时的环境因素。具体细节可以参见“本系列前一帖”。正是由于 finalize 函数调用时间点的不确定,导致了后面提到的某些缺点。

谁来调用?

  说完何时调用,咱接着来聊一下被谁调用?

  常见的 JVM 会通过 GC 的垃圾回收线程来进行 finalize 函数的调用。由于垃圾回收线程比较重要(人家好歹也是 JVM 的一个组成部分嘛),为了防止 finalize 函数抛出的异常影响到垃圾回收线程的运作,垃圾回收线程会在调用每一个 finalize 函数时进行 try/catch,如果捕获到异常,就直接丢弃,然后接着处理下一个失效对象的 finalize 函数。

对 finalize 函数的误解和误用

依靠 finalize 来释放资源

  很多同学寄希望于通过 finalize() 来完成类对象中某些资源的释放(比如关闭数据库连接之类)。

  有这种企图的同学,请注意看本文后面的“finalize 函数的缺点”!

使用 finalize 函数的注意事项

  下面介绍的注意事项,有些可能和性能优化关系不大,俺也一并列出来。

调用时间不确定——有资源浪费的风险

  前面已经介绍了调用机制。同学们应该认清【finalize 的调用时机是很不确定的】这样一个事实。所以,假如你把某些稀缺资源放到 finalize() 中释放,可能会导致该稀缺资源等上很久很久很久以后才被释放。这可是资源的浪费啊!

  另外,某些类对象所携带的资源(比如某些 JDBC 的类)可能本身就很耗费内存,这些资源的延迟释放会造成很大的性能问题。

可能不被调用——有资源泄漏的风险

  很多同学误以为 finalize() 总是会被调用,【其实不然】。在某些情况下,finalize() 压根儿不被调用。比如在 JVM 退出的当口,内存中那些对象的 finalize 函数可能就不会被调用了。

  俺估摸着:还有同学在打 “runFinalizersOnExit” 的主意,来确保所有的 finalize 在 JVM 退出前被调用。但是,很可惜也很遗憾,该方法从 JDK 1.2 开始,就已经被废弃了。即使该方法不被废弃,也是有很大的线程安全隐患滴!企图打这个主意的同学,趁早死了这条心吧!

  从上述可以看出,一旦你依赖 finalize() 来帮你释放资源,那可是很不妙啊(【有资源泄漏的危险】)!关于资源泄漏的严重性,俺在这里曾经提到过。很多时候,资源泄露导致的性能问题更加严重,万万不可小看。

对象可能在 finalize 函数调用时复活——有诈尸的风险

  诈尸的情况比较少见,不过俺还是稍微提一下。

  本来,只有当某个对象已经失效(没有引用),垃圾回收器才会调用该对象的 finalize 函数。但是,万一碰上某个变态的程序员,在 finalize() 函数内部再把对象自身的引用(也就是 this)重新保存在某处,也就相当于把自己复活了(因为这个对象重新有了引用,不再处于失效状态)。这种做法是不是够变态啊 :-)

  为了防止发生这种诡异的事情,垃圾回收器只能在每次调用完 finalize() 之后再次去检查该对象是否还处于失效状态。这无形中又增加了 JVM 的开销。

  随便提一下。由于 JDK 的文档中规定了(具体参见这里),JVM 对于每一个类对象实例最多只会调用一次 finalize()。所以,对于那些诈尸的实例,当它们真正死亡时,finalize() 反而不会被调用了。这看起来是不是很奇怪?

要记得自己做异常捕获

  刚才在介绍 finalize() 调用机制时提到,一旦有异常抛出到 finalize 函数外面,会被垃圾回收线程捕获并丢弃。也就是说,异常被忽略掉了(异常被忽略的危害,这里有提到)。为了防止这种事儿,凡是 finalize() 中有可能抛出异常的代码,你都得写上 try catch 语句,自己进行捕获。

要小心线程安全

  由于调用 finalize() 的是垃圾回收线程,和你自己代码的线程不是同一个线程;甚至不同对象的 finalize() 可能会被不同的垃圾回收线程调用(比如使用“并行收集器”的时候)。所以,当你在 finalize() 里面访问某些数据的时候,还得时刻留心线程安全的问题。

结论

  前面废了这么多话,最后稍微总结一下。俺窃以为:finalize 实在是 Java 的鸡肋。或许它对于【极少数】程序员有用,但对于大多数人(包括俺自个儿),这玩意儿压根儿没啥好处。大伙儿还是尽量不用为妙

本文标题:Java性能优化[04] - 关于finalize函数

文章作者:Qiming Song

发布时间:2018年01月01日 - 00:01

最后更新:2018年01月01日 - 14:01

原始链接:https://sqmwin.github.io/2018/01/01/java-attetion-3-performance-4/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!