理解Java堆转储文件

7

我已经试图找到我的应用程序中的内存泄漏问题一周了,但没有任何进展。我尝试进行堆转储并使用jhat查看转储文件并跟踪内存泄漏。

这是最佳方法吗?有什么更好的方法可以通过堆转储来跟踪内存泄漏。

感谢您的帮助。

VM使用的版本: java version "1.6.0_25" Java(TM) SE Runtime Environment (build 1.6.0_25-b06) Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode)

JVM选项: -Xmx1600m -XX:+UseParallelGC -XX:MaxPermSize=256m -Xms1600m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -verbose:gc -Xloggc:/tmp/gc.log

OOME堆栈跟踪: 无法获取。内核因内存不足错误而终止了该进程。

GC日志: 最后几行

48587.245: [GC [PSYoungGen: 407168K->37504K(476160K)] 506729K->137065K(1568448K), 3.0673560 secs] [Times: user=3.53 sys=0.00, real=3.07 secs] 
50318.617: [GC [PSYoungGen: 444224K->37536K(476416K)] 543785K->175177K(1568704K), 3.6635990 secs] [Times: user=3.70 sys=0.00, real=3.67 secs] 
50453.841: [GC [PSYoungGen: 70092K->2912K(476672K)] 207734K->178513K(1568960K), 1.0164250 secs] [Times: user=1.29 sys=0.00, real=1.02 secs] 
50454.858: [Full GC (System) [PSYoungGen: 2912K->0K(476672K)] [PSOldGen: 175601K->137776K(1092288K)] 178513K->137776K(1568960K) [PSPermGen: 60627K->60627K(74368K)], 2.0082140 secs] [Times: user=2.09 sys=0.00, real=2.01 secs] 
52186.496: [GC [PSYoungGen: 407104K->37312K(444416K)] 544880K->175088K(1536704K), 3.3705440 secs] [Times: user=3.93 sys=0.00, real=3.37 secs] 
53919.975: [GC [PSYoungGen: 444416K->37536K(476608K)] 582192K->213032K(1568896K), 3.4242980 secs] [Times: user=4.09 sys=0.00, real=3.42 secs] 
54056.872: [GC [PSYoungGen: 70113K->2880K(476480K)] 245609K->216320K(1568768K), 0.9691980 secs] [Times: user=1.19 sys=0.00, real=0.97 secs] 
54057.842: [Full GC (System) [PSYoungGen: 2880K->0K(476480K)] [PSOldGen: 213440K->99561K(1092288K)] 216320K->99561K(1568768K) [PSPermGen: 60628K->60628K(72320K)], 2.2203320 secs] [Times: user=2.23 sys=0.01, real=2.22 secs] 
55796.688: [GC [PSYoungGen: 406976K->37504K(476160K)] 506537K->137065K(1568448K), 3.2680080 secs]

更新:检查内核日志消息后发现是 oom-killer。但为什么系统会杀死该进程呢?难道不是因为该进程消耗了大量系统资源(内存)吗?


http://stackoverflow.com/questions/6754923/how-to-find-native-memory-leaks-caused-by-a-java-code - Jeffrey
1
这个链接讨论了本地代码,但我会尝试使用jconsole。 - Kathir
你尝试过在那些疑似有问题的代码部分使用“打印内存信息”吗?这可能会有所帮助。 - TheOneTeam
Java不会使用超过指定的最大堆大小。你应该将最大堆设置为应用程序所需的大小,然后确保所涉及的服务器可以向Java进程提供足够的内存。我在下面添加了更多细节。 - jtoberon
3个回答

12
关于Java内存泄露问题的提问是重复的,可以参考 这个那个等。以下是一些想法:
首先,按照上述链接中的答案,拍几个堆快照。
然后,如果你很了解整个应用程序,你可以通过查看实例计数来找出哪种类型的实例太多而无法清除。例如,如果你知道一个类是单例模式,但是在内存中看到了100个该类的实例,那么这就是有点奇怪的信号。或者,你还可以比较这些快照,找出哪些对象类型的数量随着时间的推移而增长;关键在于你正在寻找一定使用期间内的相对增长情况。
一旦你知道哪里出现了内存泄漏,就可以通过跟踪引用来找到无法收集的根引用。
最后,请记住,可能会因为堆的某些部分过小而导致OutOfMemoryError错误,而不是因为内存泄漏。检查是否这种情况:
  • 在你的问题中包括你所使用的VM。
  • 在你的问题中包括你启动Java时传递的参数。你的最小、最大和永久代堆大小是什么?你使用的是什么类型的垃圾收集器?
  • 在你的问题中包括OOM错误堆栈跟踪,以防有一些有用的信息。
  • 打开GC日志记录,以便查看哪些部分的堆正在增长。
  • 打开HeapDumpOnOutOfMemoryError参数,这样当进程死亡时就可以得到一个堆转储。

更新:我不确定你最新更新中的“内核杀死了进程并出现了内存错误”是什么意思,但我认为你可能是在说触发了Linux内存不足杀手。这与Java的OutOfMemoryError问题完全不同。要了解更多详情,请查看刚刚链接页面上的链接,包括此链接那个链接。但解决这个问题很简单:在相关服务器上使用更少的内存。我想你可以降低所涉及Java进程的最小和最大堆大小,但需要确保不会触发真正的Java OutOfMemoryErrors。你能把一些进程搬到别处吗?你是否能将内存杀手与特定进程的启动相对应?


我按照您提到的步骤进行了操作。我使用了jprofiler和jconsole。当我从jprofiler运行GC时,它恢复了所有内存,但是当我在服务器上运行'htop'时,它仍然显示tomcat使用了很多内存,并且由于内存不足而崩溃。我不知道为什么系统进程仍然显示正在使用内存。Tomcat正在CentOS上运行。 - Kathir
另外,您还没有说在比较实例计数时发现了什么。是否有任何类型的对象数量增加?就我所知,“系统进程仍显示内存已被使用”的相关性我不太理解,但是当 OOME 发生时,Java 进程可能不会停止。 - jtoberon
实例计数显示了许多HashMapEntry实例。我认为这些条目来自Hibernate,我们正在获取许多数据库记录。但最终它们都被垃圾回收了。 - Kathir
Hibernate是否缓存数据?当您比较多个堆转储时,此缓存大小是否会随时间增长?您如何知道数据将从缓存中驱逐--例如是否有最大大小,缓存是否使用弱引用等?此外,如果您正在加载大量数据,则可能已将最大堆大小设置得太低。 - jtoberon
好的。让我尝试使用更少的内存设置并查看。 - Kathir
显示剩余2条评论

2
“内存泄漏可以通过以下3个简单步骤解决:

步骤1:在早期阶段捕获堆转储

启动您的应用程序。让它接收真实流量10分钟。此时捕获堆转储。堆转储基本上是您的内存快照。它包含所有驻留在内存中的对象,存储在这些对象中的值以及这些对象的入站和出站引用。您可以使用以下命令捕获堆转储:”

   jmap -dump:format=b,file=<file-path> <pid> 

   where

   pid: is the Java Process Id, whose heap dump should be captured
   file-path: is the file path where heap dump will be written in to.

如果您不想使用jmap来捕获堆转储,这里有几个其他选项可以用来捕获堆转储(如链接所述)
第二步:在应用程序崩溃之前捕获堆转储
完成第一步后,让应用程序运行。在应用程序崩溃之前再次捕获堆转储。通常情况下,在它崩溃之前捕获堆转储可能是具有挑战性的,因为我们不知道应用程序何时会崩溃。是在30分钟后、3小时后还是3天后?因此,最好使用以下JVM属性启动应用程序:
    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<file-path>

    file-path: is the file path where heap dump will be written in to.

该属性将在应用程序遇到OutOfMemoryError时立即触发堆转储。
第三步:分析堆转储
导致内存泄漏的对象会随着时间的推移而增长。如果您可以识别出在步骤#1和步骤#2中捕获的堆转储之间大小已经增长的对象,则这些对象将导致内存泄漏。
您可以考虑使用堆转储分析器工具,例如HeapHero.ioEclipse MAT。当您将堆转储加载到任何工具中时,将有一个报告内存中最大对象的部分。比较此部分在步骤#1和步骤#2中捕获的堆转储之间。如果您注意到任何对象的异常增长,则它们就是导致应用程序内存泄漏的对象。

0

我建议您查看我关于这个主题的原始文章。Java堆转储分析起初可能很复杂,但使用Eclipse Memory Analyzer等工具可以简化该过程。

JVM堆转储在以下情况下非常有用:

  • 识别Java级内存泄漏。
  • 识别Java级类加载器泄漏。
  • 了解在特定负载/流量场景下您的Java应用程序内存占用情况。

参考资料:Java heap dump: are you up to the task?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接