![在 Java 线程上触发 OOM killer 日志时进行解释](https://linux22.com/image/787997/%E5%9C%A8%20Java%20%E7%BA%BF%E7%A8%8B%E4%B8%8A%E8%A7%A6%E5%8F%91%20OOM%20killer%20%E6%97%A5%E5%BF%97%E6%97%B6%E8%BF%9B%E8%A1%8C%E8%A7%A3%E9%87%8A.png)
当 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
因此我想知道:
- oom-killer 消息的 PID 来自哪里?
- 为什么在这种情况下线程与其托管 JVM 进程分开处理?
- 如果将 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 终止的情况。