Tomcat 进程在交换空间用完后被 Linux 内核杀死;没有任何 JVM OutOfMemory 错误

Posted

技术标签:

【中文标题】Tomcat 进程在交换空间用完后被 Linux 内核杀死;没有任何 JVM OutOfMemory 错误【英文标题】:Tomcat process killed by Linux kernel after running out of swap space; don't get any JVM OutOfMemory error 【发布时间】:2013-06-16 16:31:14 【问题描述】:

我正在对一个 tomcat 服务器进行负载测试。服务器有 10G 物理内存和 2G 交换空间。堆大小(xms 和 xmlx)之前设置为 3G,服务器运行良好。由于我仍然看到剩余的可用内存很多且性能不佳,我将堆大小增加到 7G 并再次运行负载测试。这次我观察到物理内存很快被吃光了,系统开始消耗交换空间。后来tomcat用完swap空间就崩溃了。我在启动 tomcat 时包含了-XX:+HeapDumpOnOutOfMemoryError,但我没有得到任何堆转储。当我查看/var/log/messages 时,我看到了kernel: Out of memory: Kill process 2259 (java) score 634 or sacrifice child

为了提供更多信息,当堆大小设置为 3G 和 7G 时,这是我从 Linux top 命令看到的内容

xms&xmx = 3G(效果很好):

启动tomcat之前:

Mem:  10129972k total,  1135388k used,  8994584k free,    19832k buffers
Swap:  2097144k total,        0k used,  2097144k free,    56008k cached

启动tomcat后:

Mem:  10129972k total,  3468208k used,  6661764k free,    21528k buffers
Swap:  2097144k total,        0k used,  2097144k free,   143428k cached
PID  USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
2257 tomcat    20   0 5991m 1.9g  19m S 352.9 19.2   3:09.64 java

开始加载10分钟后:

Mem:  10129972k total,  6354756k used,  3775216k free,    21960k buffers
Swap:  2097144k total,        0k used,  2097144k free,   144016k cached
PID  USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
2257 tomcat    20   0 6549m 3.3g  10m S 332.1 34.6  16:46.87 java

xms&xmx = 7G(导致tomcat崩溃):

启动tomcat之前:

Mem:  10129972k total,  1270348k used,  8859624k free,    98504k buffers
Swap:  2097144k total,        0k used,  2097144k free,    74656k cached

启动tomcat后:

Mem:  10129972k total,  6415932k used,  3714040k free,    98816k buffers
Swap:  2097144k total,        0k used,  2097144k free,   144008k cached
PID  USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
2310 tomcat    20   0  9.9g 3.5g  10m S  0.3 36.1   3:01.66 java

开始加载 10 分钟后(就在 tomcat 被杀死之前):

Mem:  10129972k total,  9960256k used,   169716k free,      164k buffers
Swap:  2097144k total,  2095056k used,     2088k free,     3284k cached
PID  USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
2310 tomcat    20   0 10.4g 5.3g  776 S  9.8 54.6  14:42.56 java

Java 和 JVM 版本:

Java(TM) SE Runtime Environment (build 1.7.0_21-b11)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)

Tomcat 版本:

6.0.36

Linux 服务器:

Red Hat Enterprise Linux Server release 6.4 (Santiago)

所以我的问题是:

    为什么会出现这个问题?当 JVM 内存不足时,为什么没有抛出 OutOfMemoryError?为什么它直接使用交换? 为什么topRES显示java正在使用5.3G内存,消耗的内存更多?

我已经调查和搜索了一段时间,仍然找不到这个问题的根本原因。非常感谢!

【问题讨论】:

更好的问题是为什么 Tomcat 使用这么多内存?您仍然可以通过发送进程 SIGQUIT (kill -3) 或使用jmap 来获得堆转储。如果大部分内存都来自一个地方,那么 Eclipse MAT 可能是分析转储的最简单方法。 谢谢!这是一个更好的问题。我一定会看看堆转储。但看起来还有很多堆外内存使用情况。有什么方法可以监控它们吗? 不确定它是否有帮助,但VMMap 可以显示 JVM 的本机内存使用情况。您可能首先要检查与 netstat 的打开连接 - 使用本机内存的最可能导致泄漏的原因是 Web 容器线程。 【参考方案1】:

为什么会出现这个问题? JVM内存不足为什么没有抛出OutOfMemoryException?

内存不足的不是 JVM。 主机操作系统已耗尽内存相关资源,正在采取剧烈行动。操作系统无法知道进程(在本例中为 JVM)在响应更多内存请求时被告知“否”时能够以有序的方式关闭。它必须硬杀某些东西,否则整个操作系统都会挂起的严重风险。

无论如何,您没有看到 OOME 的原因是这不是 OOME 情况。实际上,JVM 已经被操作系统给予了太多内存,并且没有办法收回。这就是操作系统必须通过硬杀进程来处理的问题。

为什么直接使用swap?

它使用交换,因为整个系统的总虚拟内存需求不适合物理内存。这是 UNIX / Linux 操作系统的正常行为。

为什么 top RES 显示 java 使用 5.3G 内存,消耗的内存要多得多

RES 数字可能有点误导。他们指的是进程当前正在使用的物理内存量......不包括与其他进程共享或可共享的东西。 VIRT 编号与您的问题更相关。它说您的 JVM 正在使用 10.4g 的虚拟内存......这比您系统上的可用物理内存更多


正如另一个答案所说,令人担忧的是,您没有获得 OOME。即使你确实得到了一个,用它做任何事情都是不明智的。 OOME 可能会对您的应用程序/容器造成附带损害,这些损害难以检测和恢复。这就是为什么 OOME 是 Error 而不是 Exception


建议:

不要尝试使用比物理内存多得多的虚拟内存,尤其是在 Java 中。当 JVM 运行完整的垃圾回收时,它会以 随机 的顺序多次 接触其 VM 页面的大部分。如果您过度分配内存,很可能会导致抖动,从而影响整个系统的性能。

请增加系统的交换空间。 (但这可能无济于事......)

不要尝试从 OOME 中恢复。

【讨论】:

谢谢!这就说得通了。但我仍然不明白为什么当堆大小设置为 3G 和 7G 时会有如此大的差异。通过在启动tomcat之前查看内存使用情况,我认为操作系统应该能够处理7G堆。 您的 JVM实际上使用 10.4G。也许你有很多堆外内存使用在幕后。另请注意,在您使用较小堆的情况下,请求的堆大小与观察到的 VIRT 大小之间存在相似的约 3.5G 差异。 再次感谢。我才意识到他们都有3.5G的差异。那么有没有办法可以在幕后找出那些堆外内存使用情况?另外,我正在阅读另一篇文章,这表明 RSS 比 VIRT 更相关。我现在很困惑...***.com/questions/561245/… @baggiowen - RESVIRT 的相关性取决于您提出的问题/您要解决的问题。出于目的,VIRT 更相关,但USED 是最好的衡量标准。但无论哪种方式,如果您要从统计数据中得出准确的结论,需要了解 Linux 虚拟内存的工作原理,以及这些数字的实际含义。对于后者,请阅读“man top”……例如。【参考方案2】:

您可能在同一台计算机上还有其他进程也使用内存。在机器严重耗尽 RAM 和交换之前,您的 java 进程似乎达到了大约 5.3GB。 (然后其他进程可能使用 12GB-5.3GB = 6.7GB)所以你的 linux 内核牺牲了你的 java 进程来保持其他进程的运行。永远不会达到 Java 内存限制,因此您不会收到 OutOfMemoryException。

考虑您需要在整台机器上运行的所有进程,并相应地调整您的 Xmx 设置(足以为所有其他进程留出空间)。也许 5GB?

在任何情况下,计数 OutOfMemoryExceptions 被传递是一种非常糟糕的代码气味。如果我没记错的话,即使是单个 OutOfMemoryException 也会使 JVM 处于“所有赌注都关闭”状态,并且可能应该重新启动以免变得不稳定。

【讨论】:

感谢您的回复!但是当我执行top -a(按内存使用情况排序)时,我没有看到任何其他进程消耗大量内存。而且如果你在我启动tomcat之前查看内存使用情况,只使用了大约1G内存。 这是一个很好的建议,但是“OutOfMemoryException 会使 JVM 处于‘all-bets-are-off’状态”部分是不正确的。首先,它是OutOfMemoryError,但更重要的是,这是一个不会造成固有不稳定的有序过程。问题是如果没有更多内存,您的程序不可能做任何有用的事情。但它没有任何损坏或不稳定。 OutOfMemoryError 的最大问题是它可能在运行时发生在一些 java.* 框架类或另一个第三方库的深处,这些库可能没有准备好进行清理的异常处理程序,例如例子。至少这是我多年前在与此类错误引发的第三方库的一些不稳定作斗争时得到的解释。 @faffaffaff - 或者 OOME 可能发生在某个工作线程上......然后死亡,让应用程序的其他部分处于等待通知等永远不会到达的状态。 @StephenC 好点,想一想,这是问题的很大一部分,因为:工作线程退出,因为没有任何东西捕捉到意外的 OOME,事情开始堆积或卡住。

以上是关于Tomcat 进程在交换空间用完后被 Linux 内核杀死;没有任何 JVM OutOfMemory 错误的主要内容,如果未能解决你的问题,请参考以下文章

Tomcat 中的 HyperSQL 数据库在 Linux 重启后被删除

如何查看Linux下的Tomcat的进程?

复习-进程的调度算法

Linux 交换空间优化(swap 优化)(积极使用交换空间占比,可能会使程序运行缓慢!)

Linux系统清除缓存

linux学习之缓存机制