Java线程触发OOM Killer时的日志解读及相关技术疑问
咱们来一步步拆解你遇到的这个Java线程触发OOM Killer的问题,逐一解答你的疑问:
1. OOM Killer消息里的PID来自哪里?
这个PID其实是Linux轻量级进程(LWP,Light Weight Process)的ID,也就是Java线程对应的内核级线程ID。
Java在Linux平台默认采用原生线程模型,每个Java线程都会对应一个内核层面的LWP——你可以把它理解成内核视角下的“线程”。内核在触发OOM Killer时,会把当前发起内存申请操作的那个LWP的ID打出来,而不是整个JVM进程的PID(也就是线程组ID,tgid)。
你可以用ps -L -p <JVM进程PID>命令查看目标JVM进程下的所有LWP,就能找到日志里那个1653163对应的Java线程了。
2. 为什么线程会被单独从JVM进程里拎出来处理?
内核的OOM Killer是针对发起内存分配请求的执行实体来触发告警的,这个实体就是内核级的LWP。当Cassandra的ReadStage-150线程发起内存申请、导致系统内存不足时,内核看到的是这个LWP在申请资源,所以会把它作为OOM的触发者记录下来。
不过你也注意到了,后续的Tasks state日志里依然会列出整个JVM进程的内存情况——这是因为OOM Killer最终决定要杀死哪个实体来释放内存时,评估的是整个线程组(也就是JVM进程)的内存占用,单个LWP的内存数据不足以支撑这个决策。
3. 能不能只中断 culprit 线程而不杀死整个JVM?
理论上,Linux内核确实支持终止单个LWP,但JVM完全不兼容这种操作:
- JVM的线程模型和内核线程强绑定,强行杀死一个内核LWP会导致JVM内部的线程状态彻底混乱,大概率会引发进程崩溃、资源泄漏,甚至进程hang死无法正常退出。
- 更关键的是,OOM Killer的设计目标是释放系统级的内存资源,而Java的堆内存是属于整个JVM进程的,不是单个线程私有——就算杀死了单个线程,进程级的内存也没法被有效释放,所以OOM Killer只会把整个线程组(JVM进程)作为目标,不会去尝试杀死单个线程。
补充:日志示例对比
普通进程(snmpd)触发OOM的日志
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
这里的PID 1190既是进程ID,也是线程组ID(tgid),所以能在后续任务状态中找到对应条目。
Java线程触发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 ... 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
这里的PID 1653163是Java线程对应的LWP ID,而任务状态里的1652432是JVM进程的线程组ID(tgid)。
备注:内容来源于stack exchange,提问作者Toparvion

