如何在Java OutOfMemoryError时识别问题?

20
如何在 Java 出现 OutOfMemoryError 或者 StackOverflow 错误时识别问题。这是出现这些错误的原因或服务器崩溃的原因。
例如,我正在开发一个应用程序,该应用程序在生产环境和 UAT 上运行。突然,在生产环境中遇到了 Java OutOfMemoryError 或 StackOverflow 错误。
那么,我们如何跟踪这个问题呢?导致这个问题的原因是什么?有没有一种技术可以告诉我是哪段代码流导致了这个问题?
请解释一下,我遇到过很多次这样的问题。

如果您递归调用一个方法,请首先查看堆栈跟踪。 - Jens
跟随堆栈跟踪。如果没有找到任何问题,请在测试环境中附加分析器并运行一些负载测试。 - Juned Ahsan
堆栈跟踪可能会误导,因为最消耗内存的进程(罪犯)可能不会引发异常。 - Danubian Sailor
6个回答

21

如果你在生产环境中遇到这个问题,而且通过堆栈跟踪或日志无法真正推断出原因,那么你需要分析其中的内容。

使虚拟机在OOM时转储

-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath="/tmp"

并将其用于分析。 内存分析器工具 (http://eclipse.org/mat/) 是进行此分析的不错独立程序。


你听说过哪些跟踪器可以分析哪个版本已经发布到生产环境了吗?有没有线程跟踪器? - Rahul Tripathi
我不确定我理解了。您的应用程序是否没有在JAR文件中组装版本号?并且它们在启动时是否不会在控制台上打印出来? - Niels Bech Nielsen
2
请注意,HeapDumpOnOutOfMemoryError标志必须以-XX:+为前缀,而不是-XX:-(某些文档有误)。我正在编辑您的答案以修正此问题。 - Eli Acherkan
@ntoskrnl 例如,这个页面让它看起来使用减号而不是加号:http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html - Eli Acherkan
那个页面没有问题,@Eli。表格列标题包括默认值,以向读者表明堆转储选项默认处于禁用状态。不过可以更清晰地强调这一点。 - Dave Jarvis

6

Oracle文档:-内存泄漏故障排除 对此有详细的解释:

这个错误是在Java堆中或堆的特定区域没有足够的空间来分配一个对象时抛出的。垃圾回收器无法提供进一步的空间以容纳新对象,而堆也无法进一步扩展。
诊断OutOfMemoryError的早期步骤是确定错误的含义。它是否意味着Java堆已满,还是意味着本机堆已满?为了帮助您回答这个问题,以下小节解释了一些可能的错误消息,并参考消息的详细部分:
线程中的异常:“main”:java.lang.OutOfMemoryError:Java堆空间
请参见3.1.1详细消息:Java堆空间。
线程中的异常:“main”:java.lang.OutOfMemoryError:PermGen空间
请参见3.1.2详细消息:PermGen空间。
线程中的异常:“main”:java.lang.OutOfMemoryError:请求的数组大小超过VM限制
请参见3.1.3详细消息:请求的数组大小超过VM限制。
线程中的异常:“main”:java.lang.OutOfMemoryError:请求字节为…。交换空间不足?
请参见3.1.4详细消息:请求字节为…。交换空间不足?
线程中的异常:“main”:java.lang.OutOfMemoryError:(本地方法)
请参见3.1.5详细消息:(本地方法)。

更新:

你可以从OpenJDK 下载 HotSpot VM源代码。如果你想要监控和跟踪Java堆空间的内存占用情况,即年轻代和老年代空间,就需要在HotSpot VM中启用详细GC。你可以在JVM启动参数中添加以下参数:

-verbose:gc –XX:+PrintGCDetails –XX:+PrintGCTimeStamps –Xloggc:<app path>/gc.log

你正在定义错误的含义,这不是问题所在。 - Juned Ahsan
@JunedAhsan:- 对此有详细的解释 - user3414693
如果问题出现在其他国家的生产环境中,而我在印度,我需要立即解决问题。在生产环境中,任何服务器日志都将生成以跟踪该问题,或者任何可以给我跟踪问题的源记录器。(因为代码已经由我完成,发送到生产环境后我只需要解决方案) - Rahul Tripathi

2

您可以使用 jvisualvm 在运行时管理您的进程。

您可以查看内存、堆空间、对象等等...

这个程序位于您的 JDKbin 目录中。


如果您正在使用Oracle JDK ... 但是jvisualvm也可以连接到其他JDK进程,例如WebSphere的示例在这里描述:http://jcraane.blogspot.de/2012/01/monitoring-ibm-jvm-with-visualvm.html 但在这种情况下最有趣的是,内存采样(分配了多少哪些对象)不可用。 - Danubian Sailor

1
最好在可以自由调试的地方(开发服务器或本地机器上)尝试复现问题。然后尝试调试,查找递归调用、堆大小以及正在创建的对象。不幸的是,要在本地机器上复制生产环境(包括负载等)并不总是容易的,因此找到这种错误的根本原因可能是一项挑战。

1
Java进程可用内存量在启动时被指定。内存分为不同的区域,其中堆和永久代是最熟悉的子区域。
通过-Xmx指定此特定进程允许的堆的最大大小,对于永久代的相应参数是-XX:MaxPermSize。90%的Java应用程序似乎需要64到512 MB的永久代才能正常工作。为了找到您的限制,请进行一些实验。
要解决此问题,您需要更改VM参数。
-Xms256m -Xmx1024m -XX:+DisableExplicitGC -Dcom.sun.management.jmxremote
-XX:PermSize=256m -XX:MaxPermSize=512m

在VM参数中添加上述两行,我相信您将不再面临这个问题。

要了解更多,请访问OutOfMemory


1

低内存配置 :-

可能您为应用程序估计的内存较少,例如您的应用程序需要2 GB的内存,但您只配置了512 MB,因此您将收到OOME(内存不足错误)

由于内存泄漏:

内存泄漏会降低堆可用内存,并可能导致内存不足错误。要了解更多信息,请阅读《Java中的内存泄漏是什么?》

内存碎片化:

堆中可能有空间,但它可能不是连续的。堆需要压缩。重新排列其内存。

过度GC开销:

某些JVM实现,例如Oracle HotSpot,在GC开销过大时会抛出内存不足错误。这个功能旨在防止几乎不断进行垃圾回收 - 例如,在释放不到2%的内存的情况下花费超过90%的执行时间进行垃圾回收。配置更大的堆最有可能解决此问题,但如果无法解决,则需要使用堆转储分析内存使用情况。

分配过大的临时对象:

程序逻辑试图分配过大的临时对象。由于JVM无法满足请求,将触发内存不足错误并中止事务。这可能很难诊断,因为没有堆转储或分配分析工具会突出显示问题。您只能确定触发错误的代码区域,并一旦发现问题的原因,修复或删除其原因。
要了解更多,请访问我的网站。

http://all-about-java-and-weblogic-server.blogspot.com/2014/02/main-causes-of-out-of-memory-errors-in.html


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