为什么不显式调用finalize()或启动垃圾收集器?
在读完这个问题后 ,我被提醒我什么时候被Java教过,并告诉他们永远不要调用finalize()或者运行垃圾收集器,因为“这是一个你永远不用担心的大黑盒子”。 有人可以将这个推理归结为几句吗? 我确信我可以在这件事上阅读Sun的技术报告,但我认为一个好的,简短的答案可以满足我的好奇心。
简短的回答:Java垃圾收集是一个非常精细的调整工具。 System.gc()是一把大锤。
Java的堆分为不同的代,每个代都使用不同的策略收集。 如果你将一个探查器附加到一个健康的应用程序,你会发现它很少需要运行最昂贵的集合,因为大多数对象都被年轻一代中速度较快的复制收集器捕获。
直接调用System.gc(),虽然在技术上不能保证做任何事情,但实际上会触发一个昂贵的,世界各地的完整堆集合。 这几乎总是错误的做法 。 你认为你正在节省资源,但实际上你是在浪费它们,没有任何理由,迫使Java重新检查你所有的活动对象“以防万一”。
如果您在关键时刻遇到GC暂停问题,那么最好配置JVM以使用并发标记/扫描收集器,该收集器专门用于最大程度地减少暂停时间,而不是尝试使用大锤来解决问题进一步打破它。
您正在考虑的Sun文档位于: Java SE 6 HotSpot™虚拟机垃圾收集调整
(你可能不知道的另一件事:在你的对象上实现finalize()方法会使垃圾收集速度变慢。首先,需要两次 GC运行才能收集对象:一个用于运行finalize(),另一个用于确保对象不是在最终确定期间不会复活。其次,具有finalize()方法的对象必须被GC视为特殊情况,因为它们必须单独收集,它们不能被批量丢弃。)
不要打扰终结者。
切换到增量垃圾收集。
如果要帮助垃圾收集器,请将对不再需要的对象的引用置空。 更少的路径=更明确的垃圾。
不要忘记(非静态)内部类实例保持对其父类实例的引用。 因此,内部类线程比你预期的要多得多。
在一个非常相关的方面,如果你正在使用序列化,并且你已经序列化了临时对象,那么你需要通过调用ObjectOutputStream.reset()来清除序列化缓存,否则你的进程会泄漏内存并最终死掉。 缺点是非瞬态对象将被重新序列化。 序列化临时结果对象可能比您想象的更麻烦!
考虑使用软引用。 如果您不知道软引用是什么,请阅读java.lang.ref.SoftReference的javadoc
除非您真的感到兴奋,否则请避开幻影参考和弱引用。
最后,如果你真的不能容忍GC使用Realtime Java。
不,我不是在开玩笑。
参考实现可以免费下载,来自SUN的Peter Dibbles书非常好读。
对于终结者来说:
- 它们实际上毫无用处。 它们不能保证及时被调用,或者实际上根本不被调用(如果GC从未运行,也不会有任何终结器)。 这意味着你通常不应该依赖它们。
- 终结器不保证是幂等的。 垃圾收集器非常谨慎,以确保它永远不会在同一个对象上多次调用
finalize()
。 使用编写良好的对象,无关紧要,但是如果编写的对象写得不好,多次调用finalize会导致问题(例如,本机资源的双重释放……崩溃)。 - 每个具有
finalize()
方法的对象也应该提供close()
(或类似)方法。 这是你应该打电话的function。 例如,FileInputStream.close()
。 当你有一个更合适的方法被你调用时,没有理由调用finalize()
。
假设终结器类似于它们的.NET同名,那么当你有可能泄漏的文件句柄等资源时,你只需要调用它们。 大多数情况下,您的对象没有这些引用,因此不需要调用它们。
尝试收集垃圾是不好的,因为它不是真正的垃圾。 您已告知VM在创建对象时分配一些内存,而垃圾收集器正在隐藏有关这些对象的信息。 在内部,GC正在对其进行的内存分配执行优化。 当你手动尝试收集垃圾时,你不知道GC想要抓住什么并摆脱它,你只是强迫它的手。 结果你弄乱了内部计算。
如果您对内部GC的内容有了更多了解,那么您可以做出更明智的决策,但之后您就错过了GC的优势。
在finalize中关闭OS句柄的真正问题是finalize以无保证的顺序执行。 但是如果你有阻止(比如sockets)的东西的句柄,你的代码可能会陷入死锁状态(根本不是微不足道的)。
所以我要以可预测的有序方式明确地关闭句柄。 基本上处理资源的代码应遵循以下模式:
SomeStream s = null; ... try{ s = openStream(); .... s.io(); ... } finally { if (s != null) { s.close(); s = null; } }
如果你编写自己的类,通过JNI和打开句柄工作,它会变得更加复杂。 您需要确保句柄已关闭(已释放)并且只会发生一次。 Desktop J2SE中经常被忽视的OS句柄是Graphics[2D]
。 即使是BufferedImage.getGrpahics()
也可能会返回指向video驱动程序的句柄(实际上是在GPU上保存资源)。 如果你不自己发布并留下垃圾收集器来完成工作 – 你可能会发现奇怪的OutOfMemory和类似的情况,当你用完video卡映射的位图但仍然有足够的内存。 根据我的经验,它经常发生在使用图形对象的紧密循环中(提取缩略图,缩放,锐化你的名字)。
基本上GC不会照顾程序员正确的资源管理责任。 它只需要记忆而不需要其他任何事情。 调用close()IMHO的Stream.finalize将更好地实现抛出exception新的RuntimeError(“垃圾收集仍然打开的流”)。 在糟糕的业余爱好者离开后,它将节省数小时和数天的调试和清洁代码。
快乐的编码。
和平。
GC在何时正确完成任务时进行了大量优化。
因此,除非您熟悉GC的实际工作方式以及它如何标记生成,否则手动调用finalize或启动GC可能会损害性能而不是帮助。
避免终结者。 无法保证及时调用它们。 在内存管理系统(即垃圾收集器)决定使用终结器收集对象之前可能需要相当长的时间。
许多人使用终结器来执行关闭套接字连接或删除临时文件等操作。 通过这样做,您可以使应用程序行为变得不可预测,并且与JVM何时将GC作为对象相关联。 这可能导致“内存不足”情况,不是由于Java堆耗尽,而是由于系统耗尽特定资源的句柄。
另外要记住的是,将调用引入System.gc()或此类锤子可能会在您的环境中显示出良好的效果,但它们不一定会转换为其他系统。 不是每个人都运行相同的JVM,有很多,SUN,IBM J9,BEA JRockit,Harmony,OpenJDK等……这个JVM都符合JCK(那些已经过官方测试的),但有很多快速做事的自由。 GC是每个人都投入巨资的领域之一。 使用锤子通常会破坏这种努力。
- Java Streams | groupingBy相同的元素
- 使用@Scheduled和@EnableScheduling但给出NoSuchBeanDefinitionException
- 在Java中查找大数的阶乘
- 使用TestExecutionListener时,Spring测试注入不起作用
- 在持久化Hibernate之前自动设置bean值(列)?
- List 无法转换为ArrayList
- Hadoop选项没有任何效果(mapreduce.input.lineinputformat.linespermap,mapred.max.map.failures.percent)
- 为什么在我的web.xml中给我一个问题
- Spring MVC; 避免url中的文件扩展名?