Java JIT编译器导致OutOfMemoryError

9
我们最近开发的一个应用程序偶尔会崩溃,并显示“java.lang.OutOfMemoryError:requested 8589934608 bytes for Chunk :: new。是否已用完交换空间?”的消息。
我在网上搜索了一下,但建议都只限于:
1. 返回先前版本的Java 2. 修改内存设置 3. 使用客户端而不是服务器模式
返回先前版本的Java意味着新版Java有bug,但我没有看到任何迹象表明存在问题。内存根本不是问题;服务器有32GB可用,Xmx设置为20,而Xms设置为10。我无法看到JVM耗尽剩余的12GB(减去机器上少量其他进程使用的内存)的情况。由于应用程序和环境的特性,我们必须使用服务器模式。
当我查看应用程序的内存和CPU使用情况时,我看到全天持续的内存使用,但就在它崩溃之前,CPU使用率飙升到100%,内存使用率从X变成X + 2GB,再变成X + 4GB,甚至达到X + 8GB(有时),然后JVM崩溃。似乎JIT编译中正在重复调整数组大小的周期。现在我看到错误发生时请求了8GB和16GB。每次发生此情况时,正在编译的方法相同。这是一个简单的方法,其中没有嵌套循环、没有递归,并且使用返回静态成员字段或实例成员字段的对象方法直接进行很少计算。
我有两个问题:
1. 有人有什么建议吗? 2. 我是否可以在测试环境中测试编译此特定方法是否存在问题,而不运行整个应用程序,直接调用JIT编译器?还是我应该启动应用程序并告诉它在更少的调用次数(如2)之后编译方法,以便迫使它几乎立即编译该方法,而不是在一天的随机时间点进行?
JVM是1.6.0_20(以前是1.6.0_0),运行在Solaris上。我知道编译是一个问题,因为:
1. 崩溃前几秒钟内的ps显示,具有与编译器线程相对应的Java线程(来自jstack)正在占用100%的CPU时间。 2. jstack显示问题出现在“CompilerThread1”守护程序[_thread_in_native,id = 34,...]中的JavaThread中。 jstack中提到的方法始终相同,是我们编写的方法。如果您查看jstack输出示例,您将知道我的意思,但由于明显的原因,我不能提供代码示例或文件名。我会说这是一个非常简单的方法。基本上是一些空值检查、两个循环进行等值检查并可能赋值,以及一些简单的方法调用。总共大约40行代码。
这个问题在2周内发生了2次,尽管应用程序每天运行并且每天重新启动。此外,这些时间应用程序没有承受重负载。

如果您能说明您使用的JVM版本和补丁级别(以及之前使用的版本),以及您的操作系统/硬件平台,那将会很有帮助。此外,请解释一下您是如何得出问题发生在JIT编译期间的结论,您是如何找出正在编译的方法...以及该方法的代码长什么样子的。 - Stephen C
你找到了满意的解决方案吗? - Thorbjørn Ravn Andersen
非常抱歉回复晚了 - 我现在已经标记了答案。 - Phil
3个回答

5
您可以通过创建一个名为 .hotspot_compiler 的文件并将其放置在您的应用程序“工作目录”中来排除某个方法被JIT编译。 只需按以下格式在文件中添加条目即可:
exclude com/amir/SomeClass someMethod

编译器的控制台输出将会如下所示:

### Excluding compile:  com.amir.SomeClasst::someMethod

如需更多信息,请阅读此文。如果您不确定应用程序的“工作目录”是什么,请使用

-XX:CompileCommandFile=/my/excludefile/location/.hotspot_compiler

在您的Java启动脚本或命令行中。

或者,如果您不确定这是JIT编译器的问题,并且想要查看是否可以在没有任何JIT编译的情况下重现问题,请使用-Xint运行您的Java进程。


1

是的,我也看到了。他的问题在于单个分配内存剩余不足。我看到了多个数组调整大小,而这些地方本来不应该有。编译一个方法不应该需要16GB的内存,对吧? - Phil
那么你可能已经尝试了你在问题中提到的建议。它们中有哪些起作用了吗? - naikus

1

这里是Oracle论坛上的另一篇文章,描述了类似的偶发性崩溃。有一个答案是通过重新配置gc的幸存者比率来解决问题的。


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