看完之后 这个问题, ,我想起了当我学 Java 时,被告知永远不要调用 Finalize() 或运行垃圾收集器,因为“这是一个你永远不需要担心的大黑匣子”。有人可以将其推理归结为几句话吗?我确信我可以阅读 Sun 关于此事的技术报告,但我认为一个好的、简短的、简单的答案将满足我的好奇心。

有帮助吗?

解决方案

简短的回答:Java 垃圾收集是一个非常精细的工具。System.gc() 是一把大锤。

Java的堆被分为不同的世代,每一代都使用不同的策略进行收集。如果您将探查器附加到一个健康的应用程序上,您会发现它很少需要运行最昂贵的集合类型,因为大多数对象都会被年轻一代中更快的复制收集器捕获。

直接调用 System.gc() 虽然在技术上不能保证执行任何操作,但实际上会触发昂贵的、停止世界的完整堆收集。这是 几乎总是做错事. 。您认为您节省了资源,但实际上您无缘无故地浪费了资源,迫使 Java 重新检查所有活动对象“以防万一”。

如果您在关键时刻遇到 GC 暂停的问题,那么您最好将 JVM 配置为使用并发标记/清除收集器,该收集器是专门为最大限度地减少暂停时间而设计的,而不是试图用大锤来解决问题,然后只是进一步打破它。

您想到的 Sun 文档在这里: Java SE 6 HotSpot™ 虚拟机垃圾收集调整

(还有一件事你可能不知道:在对象上实现 Finalize() 方法会使垃圾收集速度变慢。首先,需要 GC 运行来收集对象:一个运行finalize(),另一个确保对象在终结过程中不会复活。其次,GC 必须将具有 Finalize() 方法的对象视为特殊情况,因为它们必须单独收集,而不能批量丢弃。)

其他提示

不要为终结器烦恼。

切换到增量垃圾收集。

如果您想帮助垃圾收集器,请取消对不再需要的对象的引用。更少的路径遵循=更明确的垃圾。

不要忘记(非静态)内部类实例保留对其父类实例的引用。因此,内部类线程保留的包袱比您想象的要多得多。

与此相关的是,如果您使用序列化,并且已经序列化了临时对象,则需要通过调用 ObjectOutputStream.reset() 来清除序列化缓存,否则您的进程将泄漏内存并最终终止。缺点是非瞬态对象将被重新序列化。序列化临时结果对象可能比您想象的更混乱!

考虑使用软引用。如果您不知道什么是软引用,请阅读 java.lang.ref.SoftReference 的 javadoc

除非你真的很兴奋,否则请避开幻像引用和弱引用。

最后,如果你实在无法忍受GC,请使用Realtime Java。

不,我不是在开玩笑。

参考实现可以免费下载,SUN 的 Peter Dibbles 书籍非常值得一读。

就终结器而言:

  1. 它们实际上毫无用处。它们不能保证被及时调用,或者实际上根本不能被调用(如果 GC 从未运行,则任何终结器也不会运行)。这意味着您通常不应该依赖它们。
  2. 不保证终结器是幂等的。垃圾收集器非常小心地保证它永远不会调用 finalize() 在同一对象上多次。对于编写良好的对象,这并不重要,但是对于编写不好的对象,多次调用 Finalize 可能会导致问题(例如原生资源的双重释放...碰撞)。
  3. 每一个 对象有一个 finalize() 方法还应该提供一个 close() (或类似)方法。这是您应该调用的函数。例如。, FileInputStream.close(). 。没有理由打电话 finalize() 当你有更合适的方法时 打算由您致电。

假设终结器与其同名的 .NET 类似,那么只有当您拥有可能泄漏的资源(例如文件句柄)时,您才真正需要调用它们。大多数时候,您的对象没有这些引用,因此不需要调用它们。

尝试收集垃圾是很糟糕的,因为它并不是真正属于你的垃圾。您已经告诉虚拟机在创建对象时分配一些内存,并且垃圾收集器正在隐藏有关这些对象的信息。GC 在内部对其进行的内存分配进行优化。当您手动尝试收集垃圾时,您不知道 GC 想要保留和清除什么,您只是在强迫它采取行动。结果你搞乱了内部计算。

如果您更多地了解 GC 内部保存的内容,那么您可能能够做出更明智的决策,但这样您就错过了 GC 的好处。

在 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(“垃圾收集仍然打开的流”) 会更好地实现。在草率的业余爱好者最终失败后,这将节省数小时和数天的调试和清理代码的时间。

快乐编码。

和平。

GC 在何时正确完成事情上做了很多优化。

因此,除非您熟悉 GC 的实际工作原理以及它如何标记世代,否则手动调用 Finalize 或 start GC 可能会损害性能而非帮助。

避免终结器。无法保证他们会及时接到电话。内存管理系统(即垃圾收集器)可能需要相当长的时间才能决定使用终结器收集对象。

许多人使用终结器来执行诸如关闭套接字连接或删除临时文件之类的操作。通过这样做,您的应用程序行为变得不可预测,并与 JVM 何时对您的对象进行 GC 相关。这可能会导致“内存不足”的情况,不是因为 Java 堆耗尽,而是因为系统耗尽了特定资源的句柄。

另一件需要记住的事情是,引入对 System.gc() 或此类锤子的调用可能会在您的环境中显示良好的结果,但它们不一定会转换到其他系统。并不是每个人都运行相同的 JVM,有很多,SUN、IBM J9、BEA JRockit、Harmony、OpenJDK 等...这个 JVM 都符合 JCK(那些已经过官方测试的),但在提高速度方面有很大的自由度。GC 是每个人都大力投资的领域之一。使用锤子常常会破坏这种努力。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top