如何调试Java OutOfMemory异常?

时间:2010-12-22 18:01:04

标签: java out-of-memory coredump java1.4 thread-dump

调试java.lang.OutOfMemoryError例外的最佳方法是什么?

当我们的应用程序发生这种情况时,我们的应用服务器(Weblogic)会生成堆转储文件。我们应该使用堆转储文件吗?我们应该生成Java线程转储吗?究竟有什么区别?


更新:生成线程转储的最佳方法是什么? kill -3(我们的应用程序在Solaris上运行)是杀死应用程序并生成线程转储的最佳方法吗?有没有办法生成线程转储但不杀死应用程序?

8 个答案:

答案 0 :(得分:6)

我成功地使用Eclipse Memory Analyzer (MAT)Java Visual VM的组合来分析堆转储。 MAT提供了一些可以运行的报告,可以让您大致了解在代码中将工作重点放在哪里。 VisualVM有一个更好的界面(在我看来)用于实际检查您有兴趣检查的各种对象的内容。它有一个过滤器,您可以在其中显示特定类的所有实例,并查看它们的引用位置以及它们自己引用的内容。已经有一段时间了,因为我使用了这两种工具,现在可能有更近的功能集。当时使用两者对我来说效果很好。

答案 1 :(得分:3)

调试OutOfMemoryError问题通常非常困难。我建议使用分析工具。 JProfiler工作得很好。我过去曾经使用它,它可能非常有用,但我确信还有其他一些至少同样好的。

回答您的具体问题:

堆转储是整个堆的完整视图,即使用new创建的所有对象。如果你的内存不足那么这将是相当大的。它显示了您拥有的每种类型对象的数量。

线程转储显示每个线程的堆栈,显示转储时每个线程在代码中的位置。请记住,任何线程可能导致JVM内存不足,但它可能是一个实际抛出错误的不同线程。例如,线程1分配一个填满所有可用堆空间的字节数组,然后线程2尝试分配一个1字节的数组并引发错误。

答案 2 :(得分:3)

您还可以使用jmap / jhat附加到正在运行的Java进程。如果您必须调试实时运行的应用程序,这些(系列)工具非常有用。

你也可以让jmap作为cron任务运行,登录到一个你可以在以后分析的文件(这是我们发现调试实时内存泄漏有用的东西)

jmap -histo:live <pid> | head -n <top N things to look for> > <output.log>

Jmap也可以用于使用-dump选项生成堆转储,该选项可以通过jhat读取。

有关详细信息,请参阅以下链接 http://www.lshift.net/blog/2006/03/08/java-memory-profiling-with-jmap-and-jhat

这是书签的另一个链接 http://java.sun.com/developer/technicalArticles/J2SE/monitoring/

答案 3 :(得分:3)

  

调试java.lang.OutOfMemoryError例外的最佳方法是什么?

OutOfMemoryError描述了消息描述中的错误类型。您必须检查错误消息的描述以处理异常。

内存不足异常有多种根本原因。有关更多详细信息,请参阅oracle文档page

java.lang.OutOfMemoryError: Java heap space

原因:详细消息Java堆空间表示无法在Java堆中分配对象。

java.lang.OutOfMemoryError: GC Overhead limit exceeded

原因:详细消息“超出GC开销限制”表示垃圾收集器一直在运行,Java程序进展非常缓慢

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

原因:详细消息“请求的数组大小超过VM限制”表示应用程序(或该应用程序使用的API)尝试分配大于堆大小的数组。

java.lang.OutOfMemoryError: Metaspace

原因: Java类元数据(Java类的虚拟机内部表示)在本机内存中分配(此处称为元空间)

java.lang.OutOfMemoryError: request size bytes for reason. Out of swap space?

原因:详细消息“请求大小字节为理由。超出交换空间?”似乎是OutOfMemoryError例外。但是,当来自本机堆的分配失败并且本机堆可能接近耗尽时,Java HotSpot VM代码会报告此明显异常

java.lang.OutOfMemoryError: Compressed class space

原因:在64位平台上,指向类元数据的指针可以用32位偏移量表示(使用UseCompressedOops)。这由命令行标志UseCompressedClassPointers(默认情况下为on)控制。

如果使用UseCompressedClassPointers,则可用于类元数据的空间量固定为CompressedClassSpaceSize。如果UseCompressedClassPointers所需的空间超过CompressedClassSpaceSize,则会抛出包含详细压缩类空间的java.lang.OutOfMemoryError

注意:有多种类元数据--klass元数据和其他元数据。只有klass元数据存储在由CompressedClassSpaceSize限定的空间中。其他元数据存储在Metaspace中。

  

我们应该使用堆转储文件吗?我们应该生成Java线程转储吗?究竟有什么区别?

是。您可以使用此堆堆转储文件来使用诸如visualvmmat之类的分析工具来调试问题 您可以使用线程转储来进一步了解线程的状态。

请参阅此SE问题以了解不同之处:

Difference between javacore, thread dump and heap dump in Websphere

  

生成线程转储的最佳方法是什么? kill -3(我们的应用程序在Solaris上运行)是杀死应用程序并生成线程转储的最佳方法吗?有没有办法生成线程转储但不杀死应用程序?

kill -3 <process_id>生成线程转储,此命令不会终止java进程。

答案 4 :(得分:2)

看起来IBM提供了一种分析这些堆转储的工具:http://www.alphaworks.ibm.com/tech/heaproots;更多信息来自http://www-01.ibm.com/support/docview.wss?uid=swg21190476

答案 5 :(得分:2)

分析和修复Java中的内存不足错误非常简单。

在Java中,占用内存的对象都链接到其他一些对象,形成一棵大树。这个想法是找到树的最大分支,这通常会指出内存泄漏的情况(在Java中,内存泄漏不是在您忘记删除对象时发生,而是在您忘记忘记对象时发生,即保持一个引用某处)。

步骤1。在运行时启用堆转储

使用-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp

运行您的流程

(始终启用这些选项是安全的。根据需要调整路径,它必须可由Java用户写入)

步骤2。重现错误

让应用程序运行到发生OutOfMemoryError为止。

JVM将自动写一个java_pid12345.hprof之类的文件。

第3步。获取转储

java_pid12345.hprof复制到您的PC(它将至少与最大堆大小一样大,因此可以变得很大-必要时用gzip压缩)。

步骤4。使用IBM的Heap Analyzer或Eclipse的Memory Analyzer

打开转储文件。

堆分析器将为您显示错误发生时所有活动对象的树。 可能会在打开时直接直接指出问题。

IBM HeapAnalyzer

注意:为HeapAnalyzer提供足够的内存,因为它需要加载整个转储!

java -Xmx10g -jar ha456.jar

步骤5。确定堆使用量最大的区域

浏览对象树并识别不必要保留的对象。

请注意,还可能需要 all 个对象,这意味着您需要更大的堆。调整tune堆的大小并appropriately

步骤6。修正您的代码

确保只保留实际需要的对象。及时从集合中删除项目。确保不要保留对不再需要的对象的引用,只有这样才能对它们进行垃圾收集。

答案 6 :(得分:1)

获得查看堆转储的工具后,查看线程堆栈中处于Running状态的任何线程。它可能是那些得到错误的人之一。有时堆转储会告诉你哪个线程的错误位于顶部。

这应该指向正确的方向。然后使用标准调试技术(日志记录,调试器等)来磨练问题。使用Runtime类获取当前内存使用情况,并将其记录为执行中的方法或进程。

答案 7 :(得分:0)

我通常使用Eclipse Memory Analyzer。它显示可疑的罪魁祸首(占据大部分堆转储的对象)和生成这些对象的不同调用层次结构。一旦映射到那里,我们就可以回到代码并尝试了解代码路径中的任何位置是否存在任何可能的内存泄漏。

但是,OOM​​并不总是意味着存在内存泄漏。在稳定状态或负载下,应用程序所需的内存始终可能在硬件/ VM中不可用。例如,可能存在32位Java进程(最大内存使用~4GB),其中VM只有3 GB。在这种情况下,最初应用程序可能运行正常,但是当内存要求接近3GB时可能会遇到OOM。

正如其他人所提到的,捕获线程转储并不昂贵,但捕获堆转储却是如此。我观察到,虽然捕获堆转储应用程序(通常)冻结,只有一个kill然后重启有助于恢复。