Linux内核在交换空间耗尽后杀死Tomcat进程;不会收到任何JVM OutOfMemory错误。

14

我正在对一个Tomcat服务器执行负载测试。该服务器具有10G物理内存和2G交换空间。在此之前,堆大小(xms和xmx)已设置为3G,并且服务器运行正常。由于我仍然看到很多剩余内存且性能不佳,因此我将堆大小增加到了7G并再次运行了负载测试。这次我观察到物理内存被迅速耗尽,系统开始消耗交换空间。后来,Tomcat在耗尽交换空间后崩溃了。我在启动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
    
  • 在tomcat被杀掉之前的10分钟开启负载:

  • 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)
    

    所以我的问题是:

    1. 为什么会出现这个问题?当JVM内存耗尽时为什么没有抛出OutOfMemoryError?而且为什么直接使用交换空间(swap)?
    2. 为什么 top 命令中显示java使用了5.3G内存,实际消耗的内存要多得多?

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


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

    9
    为什么会出现这个问题?当JVM内存不足时,为什么没有抛出OutOfMemoryException?
    这不是JVM的内存已经耗尽。而是主机操作系统已经用尽了与内存相关的资源,并采取了极端措施。操作系统无法知道进程(在本例中为JVM)能否在被告知“不”以响应对更多内存的请求时有序地关闭。它必须硬杀死某些东西,否则整个操作系统就有严重风险挂起。
    总之,您看不到OOME是因为这不是OOME情况。实际上,JVM已经被操作系统给予了太多的内存,没有办法收回。这就是操作系统必须处理的问题,通过强制杀死进程来解决。
    为什么直接使用swap?
    它使用交换区是因为整个系统的虚拟内存需求超过物理内存。这是UNIX / Linux操作系统的正常行为。
    为什么top RES显示Java正在使用5.3G内存,却消耗了更多的内存?
    RES数字可能有点误导人。它所指的是进程当前正在使用的物理内存量...不包括与其他进程共享或可共享的内容。 VIRT数字对您的问题更具相关性。它表示您的JVM正在使用10.4g的虚拟内存...这比系统上可用的物理内存还要
    正如其他答案所说,让您担心没有OOME是令人担忧的。即使您确实遇到了OOME,也不明智采取任何行动。 OOME可能会对应用程序/容器造成附带损害,难以检测并且更难以恢复。这就是为什么OOME是一个Error而不是Exception的原因。
    建议:
    - 不要尝试使用显著超过物理内存的虚拟内存,特别是在Java中。当JVM运行完整垃圾收集时,它将以随机顺序多次触及其大多数VM页面。如果您已经过度分配了内存,则很可能会导致抖动,从而使整个系统的性能下降。 - 增加系统的交换空间。(但这可能没有帮助...) - 不要尝试从OOME中恢复。

    谢谢!这很有道理。但我仍然不明白的是,为什么堆大小设置为3G和7G时会有如此大的差异。通过在启动Tomcat之前查看内存使用情况,我认为操作系统应该能够处理7G堆。 - baggiowen
    1
    您的JVM实际上正在使用10.4G。也许在底层有很多非堆内存使用。还要注意,在使用较小的堆时,请求的堆大小和观察到的VIRT大小之间存在类似的3.5G差异。 - Stephen C
    1
    再次感谢。我刚刚意识到它们都有3.5G的差异。那么,有没有办法在底层找出这些非堆内存使用情况呢?此外,我正在阅读另一篇帖子,其中指出RSS比VIRT更相关。我现在很困惑... https://dev59.com/l3RB5IYBdhLWcg3wpopm?lq=1 - baggiowen
    @baggiowen - RESVIRT的相关性取决于你所提出的问题/试图解决的问题。对于这个目的,VIRT更相关,但USED是最好的度量标准。但无论如何,如果你要从统计数据中得出准确的结论,需要了解Linux虚拟内存的工作原理以及这些数字实际上意味着什么。关于后者,请阅读“man top”...例如。 - Stephen C

    1
    您可能有其他在同一台计算机上使用内存的进程。在机器无法再使用内存和交换空间之前,您的Java进程似乎达到了大约5.3GB的限制。(其他进程可能使用12GB-5.3GB=6.7GB)。因此,您的Linux内核会牺牲您的Java进程以保持其他进程运行。Java内存限制从未达到,因此您不会收到OutOfMemoryException。

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

    无论如何,在交付OutOfMemoryExceptions方面进行计数都是一种非常糟糕的代码气味。如果我记得正确的话,即使只有一个OutOfMemoryException也会使JVM处于“所有赌注全输”的状态,并且可能需要重新启动以避免不稳定。


    谢谢您的回复!但是当我执行 top -a(按内存使用排序)时,我没有看到任何其他进程消耗大量内存。如果您查看我启动tomcat之前的内存使用情况,只有大约使用了1G内存。 - baggiowen
    这是非常好的建议,但“OutOfMemoryException可能会使JVM处于‘所有赌注都关闭’状态”的部分是不正确的。首先,它应该是OutOfMemoryError,但更重要的是,这是一个有序的过程,不会创建固有的不稳定性。问题在于,如果没有更多的内存,您的程序很可能无法执行任何有用的操作。但它并没有受到任何损坏或不稳定的影响。 - erickson
    1
    OutOfMemoryError最大的问题在于它可能会在运行时深入到某个java.*框架类或另一个第三方库中发生,这些库可能没有准备好异常处理程序来进行清理。至少这是我多年前在与由此类错误触发的第三方库的不稳定性作斗争时得到的解释。 - faffaffaff
    3
    @faffaffaff - 或者 OutOfMemory 错误可能会发生在一些工作线程上...导致这些线程死亡,从而使应用程序的其他部分陷入等待通知等无法到达的困境中。 - Stephen C
    @StephenC 很好的观点,仔细想想,那是很久以前问题的重要部分:工作线程因为没有捕获到意外的 OOME 而退出,导致事情开始堆积或卡住。 - faffaffaff

    -1

    有一晚我把系统留下来,第二天早上它自己就修好了!没有任何改变,甚至没有重新启动。


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