【问题标题】:How to debug Java OutOfMemory exceptions?如何调试 Java OutOfMemory 异常?
【发布时间】:2011-05-29 13:39:18
【问题描述】:

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

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


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

【问题讨论】:

  • 你的意思是它会生成一个.hprof 文件,还是别的什么?
  • phd 格式的堆转储

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


【解决方案1】:

在 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 打开转储文件

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

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

java -Xmx10g -jar ha456.jar

第 5 步。确定最大堆使用区域

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

请注意,所有对象也可能是必需的,这意味着您需要更大的堆。大小和tuneappropriately

第 6 步。修复代码

确保只保留您真正需要的物品。及时从收藏品中取出物品。确保不要保留对不再需要的对象的引用,只有这样才能对它们进行垃圾回收。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 这些也是我使用的工具。我更喜欢 MAT,因为它比 VisualVM 需要更少的内存来加载大堆转储,即使我必须同意 VisualVM 接口更易于使用。
    【解决方案3】:

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

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

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

    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)试图分配大于堆大小的数组。 p>

    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,则可用于类元数据的空间量固定为CompressedClassSpaceSize。如果UseCompressedClassPointers 所需的空间超过CompressedClassSpaceSize,则会抛出带有详细压缩类空间的java.lang.OutOfMemoryError

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

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

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

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

    Difference between javacore, thread dump and heap dump in Websphere

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

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

    【讨论】:

      【解决方案4】:

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

      回答您的具体问题:

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

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

      【讨论】:

        【解决方案5】:

        您还可以使用 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/

        【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

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

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

          【讨论】:

          • 一般来说,找到得到 OutOfMemoryError 的线程是没有帮助的。当然,是压断骆驼脖子的那根稻草,但最后一根稻草总是应该受到责备的吗?此外,如果您不能可靠地重现问题,“标准调试技术”对您的帮助也很小。
          • 根据我的经验(我自己的号角相当可观),OutOfMemoryError 通常确实发生在导致问题的方法中。当然,您是对的,但并非总是如此。但是因为 Java 并没有真正受到内存泄漏的影响,所以这些类型的错误通常是由于在单个线程中执行的一些繁重的处理(文本或 XML)造成的,而这将与得到错误的处理相同。但你是对的,其他“无辜”线程可能会收到错误。
          【解决方案8】:

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

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-08-02
            • 2011-02-16
            • 2015-07-28
            • 2018-06-18
            • 2015-04-25
            • 2016-04-26
            • 2014-07-15
            • 1970-01-01
            相关资源
            最近更新 更多