【问题标题】:Memory profiling for Java desktop applicationJava 桌面应用程序的内存分析
【发布时间】:2010-12-17 16:27:29
【问题描述】:

我的应用程序加载了大约 1 个数据集。每次 85bm 到 100mb。应用程序的内存限制设置为 512mb,理论上这已经足够了。

但是,我发现如果在应用程序的单次运行中,我打开和关闭数据集 5 次,总内存消耗会稳步增加,直到出现内存不足错误:

 PID USER  PR NI VIRT RES SHR S %CPU %MEM TIME+   COMMAND
6882 bguiz 20 0 679m 206m 19m S 30   13.7 0:30.22 java
6882 bguiz 20 0 679m 259m 19m S 9    17.2 0:55.53 java
6882 bguiz 20 0 679m 301m 19m S 9    20.0 1:20.04 java
6882 bguiz 20 0 679m 357m 19m S 33   23.7 1:44.74 java
6882 bguiz 20 0 679m 395m 19m S 80   26.2 2:10.31 java

内存从 ~14% 增长到 ~26%。看起来像是内存泄漏。

发生的事情是,正在加载的顶级数据用于填充地图和列表等集合,然后使用更详细的数据来创建这些顶级对象的子对象,然后将它们放入依次创建子子对象。

当数据集关闭时,当前应用程序确实会尝试通过取消填充各种对象集合来清除其踪迹,然后显式调用System.gc();


无论如何,这就是我开始申请时的状态(在我之前的几年里),我被分配了这个任务。

我需要做的,是想办法找出卸载数据集后,哪些子对象和子子对象还在相互引用,并纠正它们。
显然这可以手动完成,但会非常乏味,但我觉得通过内存分析来完成这项工作会是一个更好的选择,这是我以前没有做过的事情。

我阅读了其他一些关于使用哪些内存分析工具的 SO 问题,我选择使用 Netbeans IDE 中内置的工具,因为它似乎有很好的评论,而且我仍然在 Netbeans 工作。

之前是否有人做过类似的 Java 内存分析任务,事后看来:

  • 您会给我什么具体建议?
  • 您发现哪些技术有助于解决这个问题?
  • 您发现哪些资源有助于解决此问题?

编辑: 此应用程序是标准桌面应用程序,而不是 Web 应用程序。


编辑:实施的解决方案

基本上对我有用的是将 Netbeans 的分析器与 JHAT 结合使用。

我发现 Netbeans IDE 中内置的 Profiler 在特定的分析点创建内存转储方面做得非常好,然后该工具能够按类过滤和排序并深入分析每个实例的参考。这一切都非常好。

但是,它没有为我提供比较两个堆转储的方法。我问了一个follow up question,看起来 JHAT(作为 JDK 的一部分)可以很好地完成这项工作。

Thorbjørn Ravn Andersen、Dmitry 和 Jason Gritman:你们的意见真的很有帮助,很遗憾我只能将 1 标记为正确答案,而且你们所有人都得到了我的 +1。

【问题讨论】:

  • 您使用哪个 Java 版本?
  • 当放置在离散对象中时,说 85mb 的数据会占用大约 85mb 的内存是不安全的。我们尝试将 4,000,000 条记录(每个记录由 5-10 个字段组成)加载到内存中,并在它们之间建立引用,并且内存使用量达到顶峰。
  • @Svante 我使用 Netbeans 6.7 在 JDK 1.6 上开发,目标平台是 Java 5 和 Java 6。@jt 是的,我知道,当我加载 85mb 数据集时,我预计内存使用量约为 100mb。但是,当卸载这 100mb 的大 sahre 时,并没有被取消引用(这就是问题所在)
  • 很高兴听到您的想法。最终泄漏的源头是什么?
  • @Jason,泄漏有多个来源(但只有一个触发器),到目前为止,我已经解决了大约 15% 的泄漏,仅使用内存分析器 - 这些都相当明显:例如父对象已清除对其子对象的所有引用,但忘记清除它们的事件侦听器等。我才刚刚开始使用 JHAT 将一个堆与另一个堆进行比较。

标签: java memory-management memory-leaks garbage-collection profiling


【解决方案1】:

您看到的行为不一定是内存泄漏。调用 System.gc() 只是向 VM 提示垃圾收集器应该运行(它不必运行),只要您的堆有足够的可用空间,垃圾收集器通常不会运行。因此,看到进程大小增加并不能证明垃圾收集器无法真正声明旧的数据收集。如果您不确定,我建议您对进程进行堆转储(具体如何取决于您使用的是哪个 Java VM,但它的文档应该告诉您)并在堆转储上使用分析工具来查看是否有更多对象超出预期的实例被保存在堆中并从哪里引用它们(这也可以解释内存泄漏的位置)。

作为第一次尝试,我可能会尝试使用自 Java 1.6.0_14 起可用的新 G1 garbage collector 运行程序。在正常情况下,它可能更早地到达可声明的实例,并且还具有能够将不需要的内存返回给操作系统的优势。其他 Java 垃圾收集器的问题是,从操作系统分配的内存通常要等到进程退出后才会返回。

【讨论】:

  • 他说他正在获取OutOfMemory,这意味着GC无法释放任何东西(它必须在抛出OOM之前尝试释放)
  • 你说得对,我忽略了那个细节。在这种情况下,我会配置 VM 以在 OutOfMemoryError 上写入堆转储并检查转储以找到对非必需对象的剩余引用。当 VM 诊断工具提供更容易找到此类错误的功能时,无需猜测问题可能出在哪里(如您所建议的)。
  • 嗯,使用分析器不是猜测,它以更具可读性的方式显示数据:)
【解决方案2】:

NetBeans 分析器可能是免费的最好的。它很好地完成了它的工作,没有关于使用分析器本身的特殊提示。关于您的代码,请注意缓存某些内容(尤其是静态的)的哈希映射。很多时候,它们是内存泄漏的来源。尝试使用 Wea​​kHashMap 进行缓存(粗略地说,它不会创建对缓存值的强引用)。

【讨论】:

  • 关于 HashMaps:尝试将它们分配得足够大并且负载系数相当小。每次 HashMap 必须增加它的大小时,它可能会在此过程中暂时占用内存。
  • @Dimitry,是的,确实大量使用了 HashMaps,感谢静态地图的提醒。但是,这些正在加载的东西并不是缓存,实际上需要强引用,直到数据集关闭。 @jt 感谢您的指点
【解决方案3】:

您是否在启动应用程序时更改内存分配?例如:

java -Xmx512m -Xms128m foo.Bar

据我了解,当 JVM 无法足够快地分配内存时,也会发生内存不足错误。即使它有 512m 的上限(在上面的示例中),如果 JVM 不能足够快地分配内存(超过上面最初的 128M),可能会发生内存不足错误。如果是问题,从更高的 -Xms 值开始可以缓解这种情况。还需要注意的是,Xms 和 Xmx 值是建议,而不是硬性规定。

【讨论】:

  • @jt 确实,该应用程序设置为使用-Xmx512m -Xms128m 开始,我们认为这是该特定应用程序可以使用的合理内存量。在这种情况下,硬件Xms 值当然不是问题,因为内存不足错误仅在第 5 次打开和关闭数据集时发生;这就是为什么我认为这是由于内存泄漏。
【解决方案4】:

寻找静态用作缓存的集合(Map、Set、List)。

【讨论】:

    【解决方案5】:

    我在https://stackoverflow.com/questions/1716597/java-memory-leak-detection-tools/1717260#1717260写了一个关于寻找内存泄漏技术的另一个问题的答案

    如果你听从我的建议,JProfiler 这样的工具可以让你遍历对象的参考图并查看这些对象的深度大小。这可以帮助您找到仍然保留数据的任何对象。

    我没有使用过 Netbeans,所以我无法告诉您它与我使用过的其他分析器相比如何。如果它看起来不具有该功能,您可以轻松获得 JProfiler 的试用版,它应该可以持续到您发现泄漏为止。

    【讨论】:

    • @Jason,是的,你的答案是我在问这个问题时读到的答案之一。如果 Nebans 分析器无法执行“遍历堆”之类的操作,我会考虑您对 JProfiler 的建议
    • 您的情况与我之前的回答有些不同,因为您知道哪些操作会导致泄漏,但您需要找到有问题的对象在哪里。我仍然会说通过尝试重置或取消引用可能的违规者来查看他们是否是罪魁祸首,从而使用“找到假设和测试方法”。
    【解决方案6】:

    使用 Java 6 JDK 中的 jvisualvm 附加到您的程序,然后查看您的内存去向。

    【讨论】:

    • 添加相关过滤器,以便可以在可视 vm 中查看特定于您的应用程序包的对象的计数/大小。这绝对有用。
    【解决方案7】:

    Eclipse Memory Analyzer 也是分析堆转储的良好独立工具。
    您有几个选项可以创建堆转储(例如在 OutOfMemoryExceptions 上)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-28
      • 1970-01-01
      • 2021-07-26
      • 1970-01-01
      相关资源
      最近更新 更多