【问题标题】:How to improve application to avoid heap space issues如何改进应用程序以避免堆空间问题
【发布时间】:2012-03-20 23:50:10
【问题描述】:

我有一个应用程序,它与许多自定义对象一起工作,这些对象是在方法内部创建的,并且在它们之外从不需要。整个结构(在我看来)是非常好的面向对象的,并且使用了服务、实用程序和 DI 模型。

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

我正在寻找一些简单且易于实施的解决方案、技巧和 sn-ps,以帮助应用程序处理垃圾收集和堆空间,尤其是在涉及许多与对象创建一起操作的循环时。

诸如“不要在循环中创建对象,在循环之前创建它们并在其中覆盖它”之类的东西。

【问题讨论】:

标签: java object memory heap-memory


【解决方案1】:

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

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

现在默认堆大小并没有那么大,因此要求更大的堆大小通常是完全可以接受的。

在循环中创建对象并不是特别糟糕的模式,而且在许多情况下它是非常相关的。应该避免的是在循环中重复实例化同一个对象。通常,应避免在循环中进行字符串连接,而应将其替换为在循环外创建的 StringBuilder,因为它在性能方面的效率要低得多,但在内存方面却没有。

不确定我是否真的回答了你的问题。

【讨论】:

    【解决方案2】:

    我能给你的最重要的一条建议与任何性能问题一样:

    分析、改进、重复

    使用分析器(例如VisualVM)来查找最大内存消耗的位置。改进您的代码,首先消除任何内存泄漏,然后总体上减少内存消耗。重复此过程,直到您对代码的质量和性能感到满意为止。

    编辑:

    一些交易技巧

    • 尽可能共享对象而不是复制。

    • 注意 Java 集合类(即各种 Collection<T>Map<K,V> 实现)。根据您存储的内容和正在使用的集合,您可以轻松increase your memory consumption by an order of magnitude 而不会想到它。

    • 虽然 Java(通常)不会像在 C 中遇到的那样出现内存泄漏,但 Java 代码通常会遇到对象在过期日期后仍保持活动状态的问题。

      为避免这种情况,请尽可能限制引用的范围,或在完成该对象后将其设置为 null注意:不要过度使用null-设置,尤其是在预计很快会返回的琐碎方法中。

      最重要的是:确保从任何集合中删除一个对象,当你完成它时它可能已经进入它。不这样做是OutOfMemoryError 的好方法 - 也是 Java 世界中人们所说的内存泄漏的最常见原因。

    【讨论】:

    • +1:如果 VisualVM 还不够,我也会考虑使用商业分析器。我使用 YourKit。
    【解决方案3】:

    我将首先分析您的应用程序并使用 jvisualvm(JDK 的一部分)查找内存热点。这将为您提供并指示您的对象有多大以及哪些方法调用会导致高内存使用。它还会告诉您对象在内存中停留了多长时间,这通常是一个很好的起点,因为您希望将范围缩小到尽可能短。

    下一步是通过改进设计或实施缓存来识别对象中的共性。如果您从固定存储加载数据,那么您可以使用软引用,这样当 JVM 用完堆时,这些对象将被 GC 处理(如果您对这些对象进行更改,您显然需要在删除硬引用之前保留它们)。然后,如果再次需要它们,您的应用程序只需从后备存储(数据库、文件或其他)重新加载它们。

    确保您了解 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

    【讨论】:

    • 感谢您的链接!非常有用的程序。
    【解决方案4】:

    几点:

    • 增加堆空间从根本上来说并没有错。不同的应用程序往往有不同的要求。
    • 使用分析器查看实际情况。例如在这里你可以找到堆分析器:MAT
    • 当您发现某个类的实例负责 80% 的堆消耗时:
      • 尝试查找具有相同值的共同共享变量集。这些是可以被多个对象共享的一个对象的候选对象。
      • 特别检查您是否将对相对较大的对象图的一些引用存储到比您的循环寿命长得多的变量(局部变量消耗堆栈)。
      • 让引用尽快退出范围。
      • 如果您使用内部类,请检查非静态类,因为非静态内部类包含对包含对象的引用。

    【讨论】:

    • 我真的很喜欢你的 4 个子项目符号 (+1)
    • +1 用于引用内部类。他们有很多大多数人都没有意识到的陷阱......
    【解决方案5】:

    首先,我会重新检查我的设计,重点关注所需的上层复杂性 (Landau / Big O notation)。

    其次,我会阅读 Josh Bloch's Effective Java, Item 6(消除过时的对象引用),以获得一些提示

    • “内存泄漏”的常见原因
    • 而是使用尽可能小的范围,然后取消不再需要的对象
    • 缓存和存储池。

    第三,如果你还有OOM异常,我会关注Mikko's advises

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多