就终结器而言:
finalize()
多次。对于写得好的对象来说,这没关系,但对于写得不好的对象来说,多次调用 finalize 可能会导致问题(例如,释放本机资源两次... 崩溃)。finalize()
方法的对象还应提供 close()
(或类似)方法。这是你应该调用的函数。例如,FileInputStream.close()
。当你有一个更合适的方法可以被你调用时,没有理由去调用 finalize()
。在finalize中关闭操作系统句柄的真正问题在于finalize的执行顺序没有保证。但是,如果您有阻塞事物的句柄(例如套接字),那么您的代码可能会陷入死锁状态(这并不是微不足道的)。
因此,我赞成以可预测的有序方式显式地关闭句柄。基本上,处理资源的代码应该遵循以下模式:
SomeStream s = null;
...
try{
s = openStream();
....
s.io();
...
} finally {
if (s != null) {
s.close();
s = null;
}
}
如果您编写通过JNI和打开句柄工作的自己的类,情况会变得更加复杂。您需要确保句柄已关闭(释放),并且仅发生一次。在桌面J2SE中经常被忽视的操作系统句柄是Graphics[2D]
。甚至BufferedImage.getGrpahics()
也可能会返回指向视频驱动程序(实际上在GPU上持有资源)的句柄。如果您不释放它并让垃圾收集器来完成工作,您可能会发现奇怪的OutOfMemory等情况,当您用尽映射位图的视频卡但仍有大量内存时。在我个人的经验中,这种情况在处理图形对象的紧密循环中(提取缩略图、缩放、锐化等)经常发生。
基本上,GC不负责程序员正确管理资源的责任。它只负责内存,什么都不管。在我的看法中,Stream.finalize调用close()最好实现为抛出异常new RuntimeError("garbage collecting the stream that is still open")。这将节省数小时和数天的调试和清理代码,因为那些懒散的业余爱好者留下了松散的结尾。
愉快的编码。
和平。
假设终结器类似于它们的.NET名称,那么只有在您拥有可能泄漏的资源(如文件句柄)时才真正需要调用它们。大多数情况下,您的对象没有这些引用,因此不需要调用它们。
尝试收集垃圾是不好的,因为它并不是真正属于您的垃圾。您创建对象时已经告诉 VM 分配一些内存,而垃圾收集器隐藏了关于这些对象的信息。内部 GC 对其进行的内存分配进行了优化。当您手动尝试收集垃圾时,您对 GC 希望保留和丢弃什么没有任何了解,您只是在强制执行它的操作。结果会破坏内部计算。
如果您更了解 GC 内部所持有的内容,则可能能够做出更明智的决策,但这样一来,您就错过了 GC 的好处。
垃圾回收器在决定何时适当地完成任务方面进行了大量优化。
因此,除非您熟悉GC的实际工作原理以及如何标记代,否则手动调用finalize或启动GC可能会损害性能而非帮助。
WeakReference
的使用情况比SoftReference
更强。如果Foo
需要引用Bar
以使Foo
受益,则应使用强引用。如果Foo
需要引用Bar
以使Bar
受益,则应使用弱引用。例如,每次Foo
执行某些操作时,Bar
可能希望得到通知,但是如果Foo
不必通知任何人,那么它也会很高兴。如果对Bar
的唯一引用是由那些并不真正关心它是否存在的东西持有的,那么它就不应该存在。 - supercat