我的应用程序加载大约的数据集。每次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ørnRavnAndersen,Dmitry和Jason Gritman:您的意见非常有帮助,不幸的是我只能将1标记为正确的答案,而且无论如何你们都得到了+1。
答案 0 :(得分:3)
我在https://stackoverflow.com/questions/1716597/java-memory-leak-detection-tools/1717260#1717260
上写了另一个关于寻找内存泄漏技术的问题的答案如果您遵循我的建议,JProfiler之类的工具可以让您走完对象的参考图并查看这些对象的深度。这可以帮助您找到仍然保留在数据上的任何对象。
我没有和Netbeans合作过,所以我不能告诉你它是如何与我使用的其他分析器叠加的。如果它看起来不具备该功能,您可以轻松获得JProfiler的试用版,这应该可以持续到您发现泄漏为止。
答案 1 :(得分:2)
使用Java 6 JDK中的jvisualvm附加到您的程序,看看您的内存在哪里。
答案 2 :(得分:1)
NetBeans Profiler可能是最好的免费版本。它的工作做得很好,没有关于使用探查器本身的特殊提示。关于你的代码,请注意缓存某些东西的哈希映射(特别是静态的)。它们经常是内存泄漏的来源。尝试使用WeakHashMap进行缓存(粗略地说,它不会创建对缓存值的强引用)。
答案 3 :(得分:0)
启动应用程序时是否在改变内存分配?例如:
java -Xmx512m -Xms128m foo.Bar
我的理解是,当JVM无法足够快地分配内存时,也会发生内存不足错误。即使它具有512m的上限(在上面的示例中),如果JVM无法足够快地分配内存(超过上面的初始128M),则可能发生内存不足错误。从更高的-Xms值开始可以减轻这个问题。同样重要的是要注意Xms和Xmx值是建议而不是硬性规则。
答案 4 :(得分:0)
查找用作缓存的静态集合(Map,Set,List)。
答案 5 :(得分:0)
Eclipse Memory Analyzer也是分析堆转储的一个很好的独立工具 您有几个选项可以创建堆的转储(例如,在OutOfMemoryExceptions上)。
答案 6 :(得分:-1)
您看到的行为不一定是内存泄漏。调用System.gc()只是垃圾收集器应该运行的VM的提示(它没有),只要你的堆有足够的可用空间,垃圾收集器通常就不会运行。因此,看到流程大小的增加并不能证明旧的数据集合不是垃圾收集器可以实现的。如果您不确定,我建议您对进程进行堆转储(如何依赖于您正在使用的Java VM,但其文档应该告诉您)并在堆转储上使用分析工具来查看是否有更多对象比预期更多的实例保存在堆中以及它们被引用的位置(这也可以解释内存泄漏的位置)。
作为第一次尝试,我可能尝试使用自Java 1.6.0_14以来可用的新G1 garbage collector来运行程序。在正常情况下,它可能更好地提前到达可请求的实例,并且还具有能够将不需要的内存返回到操作系统的优点。其他Java垃圾收集器的问题是,在进程退出之前,通常不会返回从OS分配的内存。