AJPFX讲解Java 性能优化[4]:关于 finalize 函数

★finalize 函数的调用机制程序员

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

◇什么时候被调用?安全

  finalize 啥时候才会被调用捏?通常来讲,要等到JVM开始进行垃圾回收的时候,它才【有可能】被调用。而 JVM 进行垃圾回收的时间点是【很是】不肯定的,依赖于各类运行时的环境因素。具体细节能够参见“本系列前一帖”。正是因为 finalize 函数调用时间点的不肯定,致使了后面提到的某些缺点。性能优化

◇谁来调用?ide

  说完什么时候调用,咱接着来聊一下被谁调用?
  常见的 JVM 会经过 GC 的垃圾回收线程来进行 finalize 函数的调用。因为垃圾回收线程比较重要(人家好歹也是 JVM 的一个组成部分嘛),为了防止 finalize 函数抛出的异常影响到垃圾回收线程的运做,垃圾回收线程会在调用每个 finalize 函数时进行 try/catch,若是捕获到异常,就直接丢弃,而后接着处理下一个失效对象的 finalize 函数。函数

★对 finalize 函数的误解和误用性能

◇把 finalize 理解为“析构函数”优化

  学过 C++ 的同窗应该都知道“析构函数”(不懂 C++ 的同窗直接跳过此小节)。C++ 析构函数是在对象离开做用域的当口,【当即】被调用的。
  不少从 C++ 转 Java 的同窗会想固然地把 Java 的 finalize 函数牵强附会成 C++ 的析构函数(二者确实有某些类似之处)。然而,现实每每不是这么美好滴。因为 Java 的 finalize 函数和 C++ 的析构函数之间有许多很是【关键性】的差别,那些把 finalize 拿来当析构函数用的同窗,是注定要碰壁滴(具体请看本文后面“finalize 函数的缺点”)。this

◇依靠 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 的鸡肋。或许它对于【极少数】程序员有用,但对于大多数人(包括俺自个儿),这玩意儿压根儿没啥好处。大伙儿仍是尽可能不用为妙。

相关文章
相关标签/搜索