18

java.lang.OutOfMemoryError调试异常的最佳方法是什么?

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


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

4

8 回答 8

12

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

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

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

运行您的流程-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp

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

步骤 2. 重现错误

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

JVM 会自动写入一个类似java_pid12345.hprof.

步骤 3. 获取转储

复制java_pid12345.hprof到您的 PC(它至少与您的最大堆大小一样大,因此可以变得非常大 - 如果需要,请使用 gzip 压缩)。

步骤 4. 使用 IBM 的堆分析器或 Eclipse 的内存分析器打开转储文件

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

IBM 堆分析器

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

java -Xmx10g -jar ha456.jar

步骤 5. 确定最大堆使用区域

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

请注意,也可能发生所有对象都是必需的,这意味着您需要更大的堆。适当调整堆的大小和调整

第 6 步:修复您的代码

确保只保留您实际需要的物品。及时从收藏品中取出物品。确保不要保留对不再需要的对象的引用,只有这样它们才能被垃圾收集。

于 2019-01-11T09:49:08.860 回答
7

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

于 2010-12-22T18:55:46.340 回答
5

java.lang.OutOfMemoryError调试异常的最佳方法是什么?

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

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

java.lang.OutOfMemoryError: Java heap space

原因:详细消息 Java heap space 指示无法在 Java heap 中分配对象。

java.lang.OutOfMemoryError: GC Overhead limit exceeded

原因:详细信息“GC overhead limit exceeded”表示垃圾收集器一直在运行,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?

原因:详细消息“request size bytes for reason. Out of swap space?” 似乎是个OutOfMemoryError例外。但是,当从本机堆分配失败并且本机堆可能接近耗尽时,Java HotSpot VM 代码会报告此明显异常

java.lang.OutOfMemoryError: Compressed class space

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

如果UseCompressedClassPointers使用 ,则可用于类元数据的空间量固定为 amount CompressedClassSpaceSizeUseCompressedClassPointers如果超过所需的空间CompressedClassSpaceSizejava.lang.OutOfMemoryError则会抛出带有详细压缩的类空间。

注意:类元数据不止一种——类元数据和其他元数据。只有 klass 元数据存储在以 . 为界的空间中CompressedClassSpaceSize。其他元数据存储在元空间中。

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

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

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

Websphere中javacore、线程转储和堆转储的区别

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

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

于 2016-05-19T15:32:20.950 回答
3

调试问题通常非常困难OutOfMemoryError。我建议使用分析工具。JProfiler 工作得很好。我过去使用过它,它非常有用,但我敢肯定还有其他的至少也一样好。

要回答您的具体问题:

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

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

于 2010-12-22T18:08:55.673 回答
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/

于 2010-12-22T20:43:52.123 回答
2

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

于 2010-12-22T18:09:41.960 回答
1

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

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

于 2010-12-22T18:29:03.200 回答
1

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

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

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

于 2015-05-05T16:37:10.403 回答