【问题标题】:Java process getting killed likely due to Linux OOM killer由于 Linux OOM 杀手,Java 进程可能被杀死
【发布时间】:2019-12-25 13:25:02
【问题描述】:

我的 java 进程在一段时间后被杀死。堆设置为 min - 2 Gb 和 max 3 Gb with parallel GC。从 pmap 命令,它显示了 40 多个 64Mb 匿名块,这似乎导致了 linux OOM 杀手。

错误

Java 运行时环境内存不足 继续。本机内存分配 (mmap) 无法映射 71827456 用于提交保留内存的字节。可能原因:系统 超出物理 RAM 或交换空间 在 32 位模式下,进程 达到大小限制 可能的解决方案: 减少 系统 增加物理内存或交换空间 检查交换后备存储是否已满 在 64 位操作系统上使用 64 位 Java 减少 Java 堆大小 (-Xmx/-Xms) 减少 Java 数量 线程 减少 Java 线程堆栈大小 (-Xss) 设置更大的代码 使用 -XX:ReservedCodeCacheSize= 缓存此输出文件可能是 被截断或不完整。

内存不足错误 (os_linux.cpp:2673), pid=21171, tid=140547280430848

JRE 版本:Java(TM) SE 运行时环境 (8.0_51-b16) (build 1.8.0_51-b16) Java VM:Java HotSpot(TM) 64 位服务器 VM(25.51-b03 混合模式 linux-amd64 压缩 oops)无法写入核心转储。 核心转储已被禁用。要启用核心转储,请尝试“ulimit -c 无限制”,然后再次启动 Java

尝试将堆减少到最小 512 Mb 和最大 2 Gb 以及 G1GC,我们看到 18 左右的 64 Mb 块数量有限,并且进程没有被杀死。

但是在堆最小 2Gb 和最大 3Gb 以及 G1GC 的情况下,我们看到大量 64 Mb 块。

根据文档,具有 2 个内核的 64 位系统的最大 64Mb 块数(malloc arenas)可以是 2*8 = 16,但我们看到超过 16 个。

【问题讨论】:

  • 这个程序可以在任何容器环境中执行吗?
  • 在虚拟机(centos9)上运行

标签: java linux-kernel glibc mmap


【解决方案1】:

这看起来不像是 Linux OOM 杀手。

您描述的症状表明您的物理内存和交换空间已用完。事实上,错误信息正是这样说的:

Java 运行时环境的内存不足,无法继续。本机内存分配 (mmap) 未能映射 71827456 字节以提交保留内存。可能的原因:

  • 系统的物理 RAM 或交换空间不足

  • 在 32 位模式下,达到了进程大小限制

虚拟内存系统通过将虚拟地址空间映射到物理 RAM 页和磁盘页的组合来工作。在任何给定时间,活动页面可能存在于 RAM 或磁盘中。如果应用程序要求更多的虚拟内存(例如使用mmap 调用),操作系统可能不得不说“不能”。这就是发生的事情。

解决方案如消息所述:

  • 获取更多内存,
  • 增加交换空间的大小,或者
  • 以各种方式限制应用程序请求的内存量。

G1GC 参数(除了最大堆大小)在很大程度上无关紧要。我的理解是最大堆大小是Java堆允许占用的(虚拟)内存总量。


如果这不是 Linux OOM 杀手,那是什么?

实际上,OOM 杀手是一种机制,可识别因执行过多分页而导致危险性能问题的应用程序。正如我在开头提到的,虚拟内存由位于 RAM 或磁盘中的页面组成。通常,应用程序不知道任何 VM 页面是否驻留在 RAM 中。操作系统只是照顾它。

如果应用程序尝试使用(读取或写入)非 RAM 驻留的页面,则会发生“页面错误”。操作系统通过以下方式处理此问题:

  • 暂停应用程序线程
  • 寻找空闲 RAM 页面
  • 正在加载读取磁盘页面到 RAM 页面
  • 正在恢复应用程序线程...然后可以访问该地址处的内存。

此外,操作系统需要维护一个“干净”页面池;即 RAM 和磁盘版本相同的页面。这是通过扫描已被应用程序修改的图像并将它们写入磁盘来完成的。

如果应用程序表现“良好”,则分页活动的数量相对较少,并且线程不会经常挂起。但是如果有很多分页,你就会达到分页 I/O 成为瓶颈的地步。在最坏的情况下,整个系统都会锁定。

OOM 杀手的目的是识别导致危险的高分页率的进程,并 .... 杀死它们。

如果 JVM 进程被 OOM 杀手杀死,它就没有机会打印错误消息(就像你得到的那样)。该进程得到一个“SIGKILL”:立即死亡。

但是...如果您查看系统日志文件,您应该会看到一条消息,指出某某进程已被 OOM 杀手杀死。

有很多资源可以解释 OOM 杀手:

【讨论】:

    【解决方案2】:

    此答案试图处理您对内存块、MALLOC_ARENA_MAX 等的观察。我不是本机内存分配器的专家。这是基于 Glibc Wiki 中的 Malloc Internals 页面。

    您已将 PrestoDB issue 8993 解读为暗示 glibc malloc 最多将为本机堆分配 MALLOC_ARENA_MAX x NOS_THREADS 内存块。根据“Malloc Internals”,这不一定是真的。

    1. 如果应用程序请求足够大的节点,实现将直接调用mmap,而不是使用arena。 (阈值由M_MMAP_THRESHOLD 选项给出。)

    2. 如果现有 arena 被填满并且压缩失败,实现将尝试通过调用 sbrkmmap 来扩大 arena。

    这些因素意味着MALLOC_ARENA_MAX 不会限制 mmap 的块数。


    请注意,arenas 的目的是在有大量线程调用mallocfree 时减少争用。但它带来了由于碎片化而丢失更多内存的风险。 MALLOC_ARENA_MAX 调优的目标是减少内存碎片。

    到目前为止,您还没有向我们展示任何明确的证据表明您的记忆问题是由碎片造成的。其他可能的解释是:

    • 您的应用程序存在本机内存泄漏,或者
    • 您的应用程序只是在使用大量本机内存。

    不管怎样,MALLOC_ARENA_MAX 调整似乎没有帮助。

    【讨论】:

    • 我看到太多(大约 50 个)64 mb 块用于 java 进程。这是来自 pmap 00007f1e64000000 65508K rw--- [ anon ] 00007f1e67ff9000 28K ----- [ anon ] 00007f1e68000000 65512K rw--- [ anon ] 00007f1e6bffa000 24K ----- [ anon ] 00007f1e6c000000 65508K rw--- [ anon ] 00007f1e6fff9000 28K ----- [ anon ] 00007f1e70000000 65512K rw--- [ anon ] 00007f1e73ffa000 24K ----- [ anon ] 00007f1e74000000 65512K rw--- [ anon ] –
    • 我不遵循你的推理。这可能是由于您的应用程序(或它使用的库之一)存在本机内存泄漏,或者只是分配了大量本机内存。 (或具有可笑的大堆栈的线程......)
    猜你喜欢
    • 2019-01-30
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 2010-12-08
    • 2012-11-22
    相关资源
    最近更新 更多