在 Java 线程上触发 OOM killer 日志时进行解释

在 Java 线程上触发 OOM killer 日志时进行解释

当 Linux OOM Killer 中断某个进程时,内核日志通常会提供有关罪魁祸首内存消耗的足够信息(即使它最终没有被杀死)。例如,当snmpd进程成为 OOM 触发器时,可以通过以下方式在日志中稍后找到其内存状态PID=1190

Jul 18 02:21:26 inm-agg kernel: snmpd invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
Jul 18 02:21:26 inm-agg kernel: CPU: 3 PID: 1190 Comm: snmpd Kdump: loaded Not tainted 5.4.17-2102.201.3.el8uek.x86_64 #2
...
Jul 18 02:21:26 inm-agg kernel: Tasks state (memory values in pages):
Jul 18 02:21:26 inm-agg kernel: [  pid  ]   uid  tgid total_vm      rss pgtables_bytes swapents oom_score_adj name
...
Jul 18 02:21:26 inm-agg kernel: [   1190]     0  1190    78491     1761   217088        0             0 snmpd

然而,当 Java 应用程序的线程发生同样的情况时(OpenJDK 64-Bit Server VM (build 25.372-b07, mixed mode)在我的情况下),日志包含一个 PID,不对应任何流程。例如,在下面的日志中,Apache Cassandra 的输入处理线程ReadStage-150已成为 OOM 触发器:

Jul 16 22:01:45 inm-agg kernel: ReadStage-150 invoked oom-killer: gfp_mask=0x100dca(GFP_HIGHUSER_MOVABLE|__GFP_ZERO), order=0, oom_score_adj=0
Jul 16 22:01:45 inm-agg kernel: CPU: 11 PID: 1653163 Comm: ReadStage-150 Kdump: loaded Not tainted 5.4.17-2102.201.3.el8uek.x86_64 #2

但是PID=1653163消息中指定的内容在其他地方没有提及:

$ journalctl -k -b -e | grep "1653163" | wc -l
1

它与 Java 进程 PID 本身没有任何共同之处(1652432):

Jul 16 22:01:45 inm-agg kernel: Tasks state (memory values in pages):
Jul 16 22:01:45 inm-agg kernel: [  pid  ]   uid  tgid total_vm      rss pgtables_bytes swapents oom_score_adj name
Jul 16 22:01:45 inm-agg kernel: [1652432]     0 1652432  7256008  5839621 49709056        0             0 java

因此我想知道:

  1. oom-killer 消息的 PID 来自哪里?
  2. 为什么在这种情况下线程与其托管 JVM 进程分开处理?
  3. 如果将 oom-killer 配置为终止 OOM 启动器,那么是否有可能(至少在理论上)只中断罪魁祸首线程而不中断整个 JVM?

答案1

Linux 内核所称的 PID (又称任务)并不严格地是 ps 或 top 所称的 PID。内核 PID 具有任务组 ID(TGID)标识“重量级”进程。在某些多线程程序中,多个 PID 共享一个 TGID 和内存,因此,在某些性能监视工具中,可能会看到 Java 进程占用超过 100% 的 CPU。

“调用 oom-killer” 标题行在开始时显示 CPU 上不幸的任务,以及到该点为止的堆栈。这可能不是导致 OOM 的“罪魁祸首”,如果未设置 sysctl oom_kill_allocating_task,它也可能不会被终止。但它可能只是进行了内存分配。

“任务状态”列表,如果通过 sysctl 启用:

转储所有符合条件的任务的当前内存状态。不在同一 memcg 中、不在同一 cpuset 中或绑定到一组不相交的 mempolicy 节点的任务不会显示。

换句话说,这是列出系统中可能被终止的进程的最好方法。请注意,“tgid”是一列,用于帮助追踪多线程线程组。启用 cgroups 后(例如使用 systemd 包含单元时),这个列表比整个系统要短得多。

内核会根据任务与系统总内存页的比例,对任务的“坏性”做出非常基本的猜测。任何“已终止进程”消息都会显示受害任务的详细信息,通过 SIGKILL 强制终止. 该信号表示整个线程组终止

这些任务都没有被证明是“罪魁祸首”。这只是内核可以轻松向您展示的内容:哪些任务刚刚占用了 CPU,一些带有 TGID 的任务可供您方便使用,并且杀死具有相对较多页面的任务可能会拯救系统。


意识到内存不足是一种可怕的情况。系统正在考虑让程序崩溃并可能导致数据丢失。没有太多空间可以耍小聪明。

无论如何,您的努力和智慧都应花在改进容量规划上。找出这些服务在服务管理器和容器中的内存占用情况。观察 cgroup 和系统范围内的内存消耗情况。想出一个内存大小算法,无论服务占用多少 GB,内核和管理占用一点,以及百分之几的安全裕度。进行调整,直到不再出现 OOM 终止的情况。

相关内容