2012-03-02 65 views
6

我有一个应用程序与许多在方法内部创建并且从不需要它们之外的自定义对象密切合作。整个结构(在我看来)非常好的面向对象,并使用服务,实用程序和DI模型。如何提高应用程序以避免堆空间问题

现在,当我运行我的第一个“大型”测试时,我很快遇到了OutOfMemoryExceptions。现在,我不只是想增加堆空间并完成它,因为我可以想象这不会解决问题,而是延迟它,直到我的应用程序增长更多,然后遇到同样的问题。

我正在寻找一些简单易用的解决方案,技巧和片段,帮助应用程序处理垃圾收集和堆空间,特别是涉及许多使用对象创建操作的循环。

就像“不要在循环中创建对象,在循环之前创建它们并在其中覆盖它”以及各种类型。

+0

我想如果你可以发布你的一些方法(至少是结构),它会容易得多。另请参阅http://stackoverflow.com/q/627784/1163434 – gawicks 2012-03-02 13:39:07

回答

2

我会通过剖析应用程序,并通过使用jvisualvm(JDK的一部分)寻找记忆热点开始了。这会给你和指示你的对象有多大以及哪些方法调用导致高内存使用。它还会告诉你你的对象在记忆中有多长时间,这通常是一个很好的起点,因为你想缩小范围尽可能短。

下一步是通过改进设计或实现缓存来识别对象中的共同点。如果你正在从一个固定的商店加载数据,那么你可以使用softreferences,这样当JVM耗尽堆,这些对象将被GCed(如果你对这些对象进行更改,你显然需要在删除硬引用前坚持它们)。然后,如果他们再次需要你的应用程序将只需要从后台存储(数据库,文件或其他)重新加载它们。

确保你知道GC是如何工作的,了解你的对象引用:

  • 强/直接
  • 幻影

这里有一些好文章其中解释了参考文献和GC:

http://www.java-tips.org/java-se-tips/java.util/using-weakhashmap-for-listener-lists.html

http://pawlan.com/monica/articles/refobjs/

http://www.kdgregory.com/index.php?page=java.refobj

+1

感谢您的链接!非常有用的程序。 – 2012-03-05 09:48:31

0

只要不再需要对象,就取消任何对象引用?只要一个对象不再提及它,GC就可以收集它,但我猜测你已经知道这一点。 GC也可以在未引用的对象图上工作(如果A只有B的引用,并且没有更多的A引用,则可以收集A和B.)

调用System.gc()几乎毫无意义,因为如果JVM需要更多内存,它将自行完成,然后使用释放的内存。如果它不能释放更多的内存,那么你会遇到OOME。

现在默认的堆大小并不是那么大,所以需要更多的堆大小通常是可以接受的。

在循环中创建对象并不是特别糟糕的模式,在很多情况下它是非常相关的。应该避免的是在循环中重复实例化同一个对象。通常,在循环中应该避免字符串串联,并由在循环外部创建的StringBuilder替换,因为它在性能方面效率低很多,但在内存方面效率却低得多。

不知道我真的回答你的问题。

4

意见,我可以给你的最重要的部分是相同的,与任何性能问题:

档案,完善,重复

使用分析器(如VisualVM),以寻找到最大的内存消耗量。改善你的代码,首先去除任何内存泄漏,然后通常减少内存消耗。重复这个过程,直到您对代码的质量和性能满意为止。

编辑:贸易的

几个技巧:

  • 分享的对象,而不是重复,在可能的情况。

  • 小心Java收集类(即各种Collection<T>Map<K,V>实现)。根据您正在存储的内容以及正在使用的收集内容,您可以轻松地在increase your memory consumption by an order of magnitude之前预期它。

  • 虽然Java通常没有与C中遇到的相同的内存泄漏,但Java代码通常在超过其过期日期保持活动的对象时遇到问题。

    为避免出现这种情况,请尽量限制参考文献的范围,或者在完成该对象时将其设置为null注意:不要过多地使用null设置,特别是在预计很快会返回的简单方法中。

    最重要的是:确保从任何收集中删除一个对象,当它完成后它可能已经被输入。不这样做对于OutOfMemoryError是一个很好的秘诀 - 也是人们在Java世界中称为内存泄漏的最常见原因。

+0

+1:如果VisualVM不够,我还会考虑使用商业分析器。我使用YourKit。 – 2012-03-02 13:51:34

5

几点:

  • 没有什么根本性的错误增加堆空间。不同的应用程序往往有不同的要求。
  • 使用事件探查器来查看真正发生了什么。例如在这里你可以找到堆分析:MAT
  • 当你发现某些类的实例负责80%的堆消耗:
    • 试图找到共同共享一套具有相同值的变量。这些候选人是可以由多个对象共享的一个对象。
    • 特别检查你是否存储了一些对比较大的对象图的引用,这些变量的存活时间比你的循环长得多(局部变量消耗堆栈)。
    • 让引用尽快退出范围。
    • 如果使用内部类,请选中非静态的类,因为非静态内部类保存对包含对象的引用。
+0

我真的很喜欢你的4个子弹(+1) – DaveFar 2012-03-02 13:47:35

+2

+1以引用内部类。他们有很多人不知道的陷阱... – thkala 2012-03-02 14:07:33

0

首先,我想我的复检设计,注重上需要复杂Landau/Big O notation)。

其次,我会读Josh Bloch's Effective Java, Item 6(淘汰落后对象引用),得到一些提示有关

  • 常见的原因为“内存泄漏”
  • ,而使用最小的范围内的可能则抵消不再需要的对象
  • 缓存和存储池。

第三,如果你还有OOM异常,我跟着Mikko's advises

相关问题