Java桌面应用程序的内存分析

我的应用程序加载大约的数据集。 每次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%。 它看起来像是内存泄漏。

发生的事情是,正在加载的顶级数据用于填充集合(如地图和列表),然后更详细的数据用于创建这些顶级对象的子对象,然后它们又创建子-sub对象。

当数据集关闭时,当前应用程序确实尝试通过System.gc();对象的各种集合来清除其轨道,然后显式调用System.gc();


无论如何,这是应用程序的状态,当我到达它(在我之前的几年),我已经分配了这个任务。

我需要做的是找到一种方法来找到卸载数据集后哪些子对象和子子对象仍然相互引用,并纠正它们。
显然这可以手动完成,但是非常繁琐,但我觉得通过内存分析来做这个更好的选择,这是我之前没有做过的事情。

我已经阅读了一些其他SO问题,询问使用哪种内存分析工具,我选择使用内置于Netbeans IDE中的内容,因为它似乎有很好的评论,而且无论如何我都在使用Netbeans。

有没有人之前进行过类似的Java内存分析任务,事后看来:

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

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


编辑:已实施的解决方案

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

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

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

ThorbjørnRavnAndersen,Dmitry和Jason Gritman:你的意见很有帮助,不幸的是我只能将1标记为正确的答案,而且你们所有人都得到了+1。

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

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

我没有和Netbeans合作过,所以我不能告诉你它是如何与我用过的其他分析器叠加的。 如果它看起来不具备该function,您可以轻松获得JProfiler的试用版,这应该持续到您发现泄漏为止。

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

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

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

 java -Xmx512m -Xms128m foo.Bar 

我的理解是,当JVM无法足够快地分配内存时,也会发生内存不足错误。 即使它具有512m的上限(在上面的示例中),如果JVM无法足够快地分配内存(超过上面的初始128M),则可能发生内存不足错误。 从更高的-Xms值开始可以减轻这个问题。 同样重要的是要注意Xms和Xmx值是建议而不是硬性规则。

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

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

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

作为第一次尝试,我可能会尝试使用自Java 1.6.0_14以来可用的新G1垃圾收集器来运行该程序。 在正常情况下,它可能更好地提前到达可请求的实例,并且还具有能够将不需要的内存返回到操作系统的优点。 其他Java垃圾收集器的问题是,在进程退出之前,通常不会返回从OS分配的内存。