java 为什么不显式调用 finalize() 或启动垃圾收集器?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28949/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why do you not explicitly call finalize() or start the garbage collector?
提问by Thomas Owens
After reading this question, I was reminded of when I was taught Java and told never to call finalize() or run the garbage collector because "it's a big black box that you never need to worry about". Can someone boil the reasoning for this down to a few sentences? I'm sure I could read a technical report from Sun on this matter, but I think a nice, short, simple answer would satisfy my curiosity.
读完这个问题后,我想起了我在教 Java 时被告知永远不要调用 finalize() 或运行垃圾收集器,因为“这是一个你永远不需要担心的大黑盒子”。有人可以将其推理归结为几句话吗?我确信我可以阅读 Sun 关于此事的技术报告,但我认为一个不错的、简短的、简单的答案会满足我的好奇心。
回答by Charles Miller
The short answer: Java garbage collection is a very finely tuned tool. System.gc() is a sledge-hammer.
简短的回答:Java 垃圾收集是一个经过精心调整的工具。System.gc() 是一把大锤。
Java's heap is divided into different generations, each of which is collected using a different strategy. If you attach a profiler to a healthy app, you'll see that it very rarely has to run the most expensive kinds of collections because most objects are caught by the faster copying collector in the young generation.
Java 的堆被分成不同的代,每一代都使用不同的策略收集。如果你将分析器附加到一个健康的应用程序,你会发现它很少需要运行最昂贵的集合,因为大多数对象都被年轻代中更快的复制收集器捕获。
Calling System.gc() directly, while technically not guaranteed to do anything, in practice will trigger an expensive, stop-the-world full heap collection. This is almost always the wrong thing to do. You think you're saving resources, but you're actually wasting them for no good reason, forcing Java to recheck all your live objects “just in case”.
直接调用 System.gc() 虽然技术上不能保证做任何事情,但在实践中会触发昂贵的、停止世界的全堆收集。这几乎总是错误的做法。您认为您在节省资源,但实际上您无缘无故地浪费它们,迫使 Java 重新检查所有活动对象“以防万一”。
If you are having problems with GC pauses during critical moments, you're better off configuring the JVM to use the concurrent mark/sweep collector, which was designed specifically to minimise time spent paused, than trying to take a sledgehammer to the problem and just breaking it further.
如果您在关键时刻遇到 GC 暂停问题,最好将 JVM 配置为使用并发标记/清除收集器,该收集器专为最大限度地减少暂停时间而设计,而不是试图用大锤解决问题而只是进一步打破它。
The Sun document you were thinking of is here: Java SE 6 HotSpot™ Virtual Machine Garbage Collection Tuning
您想到的 Sun 文档在这里:Java SE 6 HotSpot™ Virtual Machine Garbage Collection Tuning
(Another thing you might not know: implementing a finalize() method on your object makes garbage collection slower. Firstly, it will take twoGC runs to collect the object: one to run finalize() and the next to ensure that the object wasn't resurrected during finalization. Secondly, objects with finalize() methods have to be treated as special cases by the GC because they have to be collected individually, they can't just be thrown away in bulk.)
(另一件事你可能不知道:在你的对象上实现 finalize() 方法会使垃圾收集速度变慢。首先,它需要两次GC 运行来收集对象:一次运行 finalize() 和下一次确保对象不是'不会在终结过程中复活。其次,GC 必须将具有 finalize() 方法的对象视为特殊情况,因为它们必须单独收集,不能批量丢弃。)
回答by Tim Williscroft
Don't bother with finalizers.
不要打扰终结器。
Switch to incremental garbage collection.
切换到增量垃圾收集。
If you want to help the garbage collector, null off references to objects you no longer need. Less path to follow= more explicitly garbage.
如果您想帮助垃圾收集器,请取消对不再需要的对象的引用。更少的路径=更明确的垃圾。
Don't forget that (non-static) inner class instances keep references to their parent class instance. So an inner class thread keeps a lot more baggage than you might expect.
不要忘记(非静态)内部类实例保留对其父类实例的引用。因此,内部类线程保留的包袱比您预期的要多得多。
In a very related vein, if you're using serialization, and you've serialized temporary objects, you're going to need to clear the serialization caches, by calling ObjectOutputStream.reset() or your process will leak memory and eventually die. Downside is that non-transient objects are going to get re-serialized. Serializing temporary result objects can be a bit more messy than you might think!
在一个非常相关的脉络中,如果您正在使用序列化,并且您已经序列化了临时对象,您将需要通过调用 ObjectOutputStream.reset() 来清除序列化缓存,否则您的进程将泄漏内存并最终死亡。缺点是非瞬态对象将被重新序列化。序列化临时结果对象可能比您想象的要混乱一些!
Consider using soft references. If you don't know what soft references are, have a read of the javadoc for java.lang.ref.SoftReference
考虑使用软引用。如果您不知道软引用是什么,请阅读 java.lang.ref.SoftReference 的 javadoc
Steer clear of Phantom references and Weak references unless you really get excitable.
除非您真的很兴奋,否则请避开幻影引用和弱引用。
Finally, if you really can't tolerate the GC use Realtime Java.
最后,如果你真的不能容忍 GC 使用 Realtime Java。
No, I'm not joking.
不,我不是在开玩笑。
The reference implementation is free to download and Peter Dibbles book from SUN is really good reading.
参考实现可以免费下载,SUN 的 Peter Dibbles 书非常好读。
回答by Derek Park
As far as finalizers go:
就终结者而言:
- They are virtually useless. They aren't guaranteed to be called in a timely fashion, or indeed, at all (if the GC never runs, neither will any finalizers). This means you generally shouldn't rely on them.
- Finalizers are not guaranteed to be idempotent. The garbage collector takes great care to guarantee that it will never call
finalize()more than once on the same object. With well-written objects, it won't matter, but with poorly written objects, calling finalize multiple times can cause problems (e.g. double release of a native resource ... crash). - Everyobject that has a
finalize()method should also provide aclose()(or similar) method. This is the function you should be calling. e.g.,FileInputStream.close(). There's no reason to be callingfinalize()when you have a more appropriate method that isintended to be called by you.
- 它们实际上毫无用处。不能保证及时调用它们,或者根本不能保证调用它们(如果 GC 从未运行,则任何终结器也不会运行)。这意味着您通常不应该依赖它们。
- 终结器不能保证是幂等的。垃圾收集器非常小心地保证它永远不会
finalize()对同一个对象多次调用。对于编写良好的对象,这无关紧要,但是对于编写不佳的对象,多次调用 finalize 可能会导致问题(例如,本机资源的双重释放......崩溃)。 - 每个拥有
finalize()方法的对象也应该提供一个close()(或类似的)方法。这是您应该调用的函数。例如,FileInputStream.close()。我们没有理由要打电话finalize(),当你有一个更合适的方法是要由你来调用。
回答by Leo
The real problem with closing OS handles in finalize is that the finalize are executed in no guaranteed order. But if you have handles to the things that block (think e.g. sockets) potentially your code can get into deadlock situation (not trivial at all).
在 finalize 中关闭操作系统句柄的真正问题是 finalize 没有保证的顺序执行。但是,如果您拥有阻塞事物的句柄(例如套接字),则您的代码可能会陷入死锁情况(根本不是微不足道的)。
So I'm for explicitly closing handles in a predictable orderly manner. Basically code for dealing with resources should follow the pattern:
所以我赞成以可预测的有序方式明确关闭句柄。处理资源的基本代码应该遵循以下模式:
SomeStream s = null;
...
try{
s = openStream();
....
s.io();
...
} finally {
if (s != null) {
s.close();
s = null;
}
}
It gets even more complicated if you write your own classes that work via JNI and open handles. You need to make sure handles are closed (released) and that it will happen only once. Frequently overlooked OS handle in Desktop J2SE is Graphics[2D]. Even BufferedImage.getGrpahics()can potentially return you the handle that points into a video driver (actually holding the resource on GPU). If you won't release it yourself and leave it garbage collector to do the work - you may find strange OutOfMemory and alike situation when you ran out of video card mapped bitmaps but still have plenty of memory. In my experience it happens rather frequently in tight loops working with graphics objects (extracting thumbnails, scaling, sharpening you name it).
如果您编写自己的通过 JNI 工作的类并打开句柄,它会变得更加复杂。您需要确保句柄已关闭(释放)并且只会发生一次。桌面 J2SE 中经常被忽视的操作系统句柄是Graphics[2D]. 甚至BufferedImage.getGrpahics()可以潜在地返回指向视频驱动程序的句柄(实际上将资源保存在 GPU 上)。如果您不自己释放它并让垃圾收集器来完成这项工作 - 当您用完视频卡映射位图但仍有足够的内存时,您可能会发现奇怪的 OutOfMemory 和类似情况。根据我的经验,它在处理图形对象的紧密循环中经常发生(提取缩略图、缩放、锐化您的名字)。
Basically GC does not take care of programmers responsibility of correct resource management. It only takes care of memory and nothing else. The Stream.finalize calling close() IMHO would be better implemented throwing exception new RuntimeError("garbage collecting the stream that is still open"). It will save hours and days of debugging and cleaning code after the sloppy amateurs left the ends lose.
基本上,GC 不负责程序员正确管理资源的责任。它只关心内存,没有别的。Stream.finalize 调用 close() 恕我直言会更好地实现抛出异常 new RuntimeError("垃圾收集仍然打开的流")。在草率的业余爱好者离开后,它将节省数小时和数天的调试和清理代码。
Happy coding.
快乐编码。
Peace.
和平。
回答by Brian Lyttle
Assuming finalizers are similar to their .NET namesake then you only really need to call these when you have resources such as file handles that can leak. Most of the time your objects don't have these references so they don't need to be called.
假设终结器类似于它们的 .NET 同名,那么您只需要在您拥有可能泄漏的文件句柄等资源时才真正需要调用它们。大多数情况下,您的对象没有这些引用,因此不需要调用它们。
It's bad to try to collect the garbage because it's not really your garbage. You have told the VM to allocate some memory when you created objects, and the garbage collector is hiding information about those objects. Internally the GC is performing optimisations on the memory allocations it makes. When you manually try to collect the garbage you have no knowledge about what the GC wants to hold onto and get rid of, you are just forcing it's hand. As a result you mess up internal calculations.
尝试收集垃圾是不好的,因为它实际上不是你的垃圾。当您创建对象时,您已经告诉 VM 分配一些内存,而垃圾收集器正在隐藏有关这些对象的信息。GC 在内部对其进行的内存分配进行优化。当您手动尝试收集垃圾时,您不知道 GC 想要保留和清除什么,您只是在强迫它的手。结果你搞砸了内部计算。
If you knew more about what the GC was holding internally then you might be able to make more informed decisions, but then you've missed the benefits of GC.
如果您对 GC 内部持有的内容了解得更多,那么您可能能够做出更明智的决定,但是您已经错过了 GC 的好处。
回答by Ron
Avoid finalizers. There is no guarantee that they will be called in a timely fashion. It could take quite a long time before the Memory Management system (i.e., the garbage collector) decides to collect an object with a finalizer.
避免终结器。不能保证他们会被及时调用。内存管理系统(即垃圾收集器)决定使用终结器收集对象可能需要很长时间。
Many people use finalizers to do things like close socket connections or delete temporary files. By doing so you make your application behaviour unpredictable and tied to when the JVM is going to GC your object. This can lead to "out of memory" scenarios, not due to the Java Heap being exhausted, but rather due to the system running out of handles for a particular resource.
许多人使用终结器来完成关闭套接字连接或删除临时文件等操作。通过这样做,您可以使您的应用程序行为不可预测,并与 JVM 何时对您的对象进行 GC 相关联。这可能会导致“内存不足”的情况,这不是因为 Java 堆耗尽,而是因为系统耗尽了特定资源的句柄。
One other thing to keep in mind is that introducing the calls to System.gc() or such hammers may show good results in your environment, but they won't necessarily translate to other systems. Not everyone runs the same JVM, there are many, SUN, IBM J9, BEA JRockit, Harmony, OpenJDK, etc... This JVM all conform to the JCK (those that have been officially tested that is), but have a lot of freedom when it comes to making things fast. GC is one of those areas that everyone invests in heavily. Using a hammer will often times destroy that effort.
要记住的另一件事是,引入对 System.gc() 或此类锤子的调用可能会在您的环境中显示良好的结果,但它们不一定会转换到其他系统。不是每个人都运行相同的 JVM,有很多,SUN、IBM J9、BEA JRockit、Harmony、OpenJDK 等等……这个 JVM 都符合 JCK(已经过官方测试的那些),但是有很多使事情变得更快的自由。GC 是每个人都大力投资的领域之一。使用锤子通常会破坏这种努力。
回答by chakrit
The GC does a lot of optimization on when to properly finalize things.
GC 对何时正确完成事情做了很多优化。
So unless you're familiar with how the GC actually works and how it tags generations, manually calling finalize or start GC'ing will probably hurt performance than help.
因此,除非您熟悉 GC 的实际工作方式以及它如何标记代,否则手动调用 finalize 或 start GC'ing 可能会损害性能而不是帮助。

