简体   繁体   English

为什么不显式调用finalize()或启动垃圾收集器?

[英]Why do you not explicitly call finalize() or start the garbage collector?

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". 读完这个问题之后 ,我想起了我什么时候被Java教过,并且告诉他们永远不要调用finalize()或运行垃圾收集器,因为“这是一个你永远不用担心的大黑盒子”。 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. 我确信我可以阅读Sun关于此事的技术报告,但我认为一个简单,简短的答案可以满足我的好奇心。

The short answer: Java garbage collection is a very finely tuned tool. 简短的回答:Java垃圾收集是一个非常精细的调整工具。 System.gc() is a sledge-hammer. System.gc()是一把大锤。

Java's heap is divided into different generations, each of which is collected using a different strategy. Java的堆分为不同的代,每个代都使用不同的策略收集。 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. 如果你将一个探查器附加到一个健康的应用程序,你会发现它很少需要运行最昂贵的集合,因为大多数对象都被年轻一代中速度较快的复制收集器捕获。

Calling System.gc() directly, while technically not guaranteed to do anything, in practice will trigger an expensive, stop-the-world full heap collection. 直接调用System.gc(),虽然在技术上不能保证做任何事情,但在实践中会触发一个昂贵的,世界各地的完整堆集合。 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”. 你认为你正在节省资源,但实际上你没有充分的理由浪费它们,迫使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™虚拟机垃圾收集调整

(Another thing you might not know: implementing a finalize() method on your object makes garbage collection slower. Firstly, it will take two GC 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(),另一个运行以确保对象不是在最终确定期间不会复活。其次,具有finalize()方法的对象必须被GC视为特殊情况,因为它们必须单独收集,它们不能被批量丢弃。)

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. 在一个非常相关的情况下,如果您正在使用序列化,并且您已经序列化了临时对象,那么您将需要通过调用ObjectOutputStream.reset()来清除序列化缓存,否则您的进程将泄漏内存并最终死亡。 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! 序列化临时结果对象可能比您想象的更麻烦!

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书非常好读。

As far as finalizers go: 对于终结者来说:

  1. 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). 它们不能保证及时被调用,或者实际上根本不被调用(如果GC从未运行,也不会有任何终结器)。 This means you generally shouldn't rely on them. 这意味着你通常不应该依赖它们。
  2. 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. 垃圾收集器非常谨慎,以确保它永远不会在同一个对象上多次调用finalize() With well-written objects, it won't matter, but with poorly written objects, calling finalize multiple times can cause problems (eg double release of a native resource ... crash). 使用编写良好的对象,无关紧要,但是如果编写的对象写得不好,多次调用finalize会导致问题(例如,本机资源的双重释放......崩溃)。
  3. Every object that has a finalize() method should also provide a close() (or similar) method. 每个具有finalize()方法的对象也应该提供close() (或类似)方法。 This is the function you should be calling. 这是你应该打电话的功能。 eg, FileInputStream.close() . 例如, FileInputStream.close() There's no reason to be calling finalize() when you have a more appropriate method that is intended to be called by you. 我们没有理由被调用finalize()时,你有打算被你称为一个更合适的方法。

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. 假设终结器类似于它们的.NET同名,那么当你有可能泄漏的文件句柄等资源时,你只需要调用它们。 Most of the time your objects don't have these references so they don't need to be called. 大多数情况下,您的对象没有这些引用,因此不需要调用它们。

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. 您已告知VM在创建对象时分配一些内存,并且垃圾收集器正在隐藏有关这些对象的信息。 Internally the GC is performing optimisations on the memory allocations it makes. 在内部,GC正在对其进行的内存分配执行优化。 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. 当你手动尝试收集垃圾时,你不知道GC想要抓住什么并摆脱它,你只是强迫它的手。 As a result you mess up internal calculations. 结果你弄乱了内部计算。

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的优势。

The real problem with closing OS handles in finalize is that the finalize are executed in no guaranteed order. 在finalize中关闭OS句柄的真正问题是finalize以无保证的顺序执行。 But if you have handles to the things that block (think eg sockets) potentially your code can get into deadlock situation (not trivial at all). 但是如果你有阻止(比如插座)的东西的句柄,你的代码可能会陷入死锁状态(根本不是微不足道的)。

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. 如果你编写自己的类通过JNI和打开句柄工作,它会变得更加复杂。 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] . Desktop J2SE中经常被忽略的OS句柄是Graphics[2D] Even BufferedImage.getGrpahics() can potentially return you the handle that points into a video driver (actually holding the resource on GPU). 甚至BufferedImage.getGrpahics()也可能会返回指向视频驱动程序的句柄(实际上是在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. 如果你不自己发布并留下垃圾收集器来完成工作 - 你可能会发现奇怪的OutOfMemory和类似的情况,当你用完视频卡映射的位图但仍然有足够的内存。 In my experience it happens rather frequently in tight loops working with graphics objects (extracting thumbnails, scaling, sharpening you name it). 根据我的经验,它经常发生在使用图形对象的紧密循环中(提取缩略图,缩放,锐化你的名字)。

Basically GC does not take care of programmers responsibility of correct resource management. 基本上GC不会照顾程序员正确的资源管理责任。 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"). 调用close()IMHO的Stream.finalize将更好地实现抛出异常新的RuntimeError(“垃圾收集仍然打开的流”)。 It will save hours and days of debugging and cleaning code after the sloppy amateurs left the ends lose. 在业余爱好者最终输掉后,它将节省数小时和数天的调试和清理代码。

Happy coding. 快乐的编码。

Peace. 和平。

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或启动GC可能会损害性能而不是帮助。

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 (ie, 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. 通过这样做,您可以使应用程序行为变得不可预测,并且与JVM转向GC对象时绑定在一起。 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. 这可能导致“内存不足”情况,不是由于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. 另外要记住的是,将调用引入System.gc()或此类锤子可能会在您的环境中显示出良好的效果,但它们不一定会转换为其他系统。 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. 不是每个人都运行相同的JVM,有很多,SUN,IBM J9,BEA JRockit,Harmony,OpenJDK等......这个JVM都符合JCK(已经过官方测试的那些),但有很多快速做事的自由。 GC is one of those areas that everyone invests in heavily. GC是每个人都投入巨资的领域之一。 Using a hammer will often times destroy that effort. 使用锤子通常会破坏这种努力。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM