java oom-killer_Linux应用进程消失之谜--Java进程与OOM Killer

“Java进程咋又突然没了?还没任何报错,都好几天了好奇怪啊。” 上午刚上班不久,旁边的同事就遇到了棘手的技术问题。大概打听下,原来是某个Java应用在执行某个耗时的定时任务的过程中,大概率进程会突然退出,而且应用日志、中间件日志都找不到任何异常。听起来还挺有意思,我默默地登上了出问题的docker容器。

0x01 OOM killer

登上机器后,查看应用和中间件日志,确实没有看到问题。我怀疑是JVM OOM了但没有配置输出,正想加上OOM时的堆栈输出参数,但发现应用启动命令已经包含类似的参数:-XX:+HeapDumpOnOutOfMemoryError -XX:ErrorFile=./hs_err_pid.log -XX:HeapDumpPath=./java_pid.hprof。 看来从应用层面查不到什么了,那就再看看系统日志吧。 使用dmesg -T | grep java查看系统日志

2dd41a7222f00850b7044f8702c1e687.png 果然,从dmesg倒数第二行返回的信息来看,确实是由于oom导致的,但是这里的oom并非JVM的oom,而是Linux系统的oom。 >Killed process xxx(java), total-vm:7539096kB, anon-rss:3919048kB file-rss:17312kB, shmem-rss:0kB。

在这台物理内存为4GB的机器上,

`total-vm`是已经分配给java进程的虚拟内存数量(7539096kb=7.02GB)

`anon-rss`是java进程物理内存使用量(3919048kB=3.65GB)

`file-rss`是java进程映射到设备或文件系统的使用量(17312kB=16.51MB)

网上已经有很多介绍Linux oom killer的文章了,这里只简单概括:Linux系统在分配物理内存时,如果内存不足(什么时候不足?)oom killer会选择oom score得分最高的进程杀掉以释放内存。

0x02 JVM OOM vs. Linux OOM

看起来问题很明确了,是Linux的oom killer杀掉了Java进程。但是我还是有个疑问,同样是OOM,为什么没有触发JVM的OOM呢?带着这个疑问,继续了解了Linux的内存分配机制。

0x02.1 overcommit与oom killer

以下简单说一下我的理解,真实分配机制要复杂的多。有不对的地方请大家指正。 Linux有一套虚拟内存机制,当进程向系统申请内存时,总体上系统可以有两种方式答复进程: a.检查是否真的还有足够的内存(实际值计算一般等于Swap+RAM*overcmmit_ratio)满足需求,如果有就满足分配,如果没有就以分配失败答复集成; b.不检查,直接分配。

这里的分配指的都是进程的虚拟内存地址的增长。这里的b叫做"Overcommit",也就是发生了超过系统可接受内存范围的分配。这里的b其实是基于一种乐观的估计,因为申请的内存不一定用到。但是一旦进程需要使用所能提供的实际内存时,就会导致OOM,此时oom killer就会通过排序oom score牺牲掉一个或多个得分最高的进程,以此来释放内存。

再强调一次理解Overcommit的关键点,内存申请不等于内存分配,内存使用时才真正分配。

0x02.2 JVM OOM

那么JVM的OOM呢?

>One common indication of a memory leak is the java.lang.OutOfMemoryError exception. Usually, this error is thrown when there is insufficient space to allocate an object in the Java heap. In this case, The garbage collector cannot make space available to accommodate a new object, and the heap cannot be expanded further. Also, this error may be thrown when there is insufficient native memory to support the loading of a Java class. In a rare instance, a java.lang.OutOfMemoryError may be thrown when an excessive amount of time is being spent doing garbage collection and little memory is being freed. 摘自Oracle内存泄露说明相关文章

简单来说,发生JVM OOM的原因有: Java堆内存不足,垃圾回收器不能给新对象腾地儿,堆也无法扩展(Java heap space); 本地内存不足以支持加载Java类(Metaspace or Perm); 不常见地,过长时间的垃圾回收没有释放多少内存也会导致OOM(GC Overhead limit exceeded)。 回头看看,其实是Linux系统把Java进程给“骗”了。当开启了允许overcommit的策略时,如果Java进程或其他任何进程申请了可能过多(超过系统能提供的)虚拟内存时,只要系统内存还足够使用,Java进程并不会发生OOM。而当Linux系统发现分配内存真的不够时,就会把oom score最靠前的Java进程悄无声息地干掉(kill -9)。

>Java老农:“爷,给俺批点地,俺要种瓜。” Linux老爷:“爷我高兴,给你4096亩地,随便耍!” Java老农种地中...1...2...4....1024... Java老农:“没有虫子骚扰太开心啦,我要这样到永...” Java卒 Linux老爷:“有没有人要地呀,老爷我有的是地啊!”

0x03 解决方案

分析完原因,其实有很多解决方案。

0x03.1 申请更大内存的机器

如果有资源,就先尝试最简单的办法吧。

0x03.2 优化代码

现在的机器一般都是容器或虚拟机上只要只跑一个应用,如果发生OOM,最根本的解决之道还是要回归到代码上。

0x03.3 禁止Overcommit

让系统在申请时就报错,保守也保险。及早暴露问题,及早进行修复。同时可以修改overcommit_ratio的值改变CommitLimit。 配置参见附录。

0x03.4 禁止OOM Killer杀掉关键的应用进程

对于一些多应用进程混用的机器,可以保护关键进程不被kill掉。 sudo echo -1000 > /proc/$pid/oom_score_adj

0x03.5 禁止OOM Killer

修改panic_on_oom参数可以关闭OOM Killer。玩玩还可以,线上系统不建议用,否则系统死给你看。

附录:

1 Linux的Overcommit的策略

可在/proc/sys/vm/overcommit_memory配置或查看。

>0:默认值。启发式策略,比较严重的Overcommit将不能满足,而轻微的Overcommit将被允许。另外,root能Overcommit的值比普通用户要稍微多些,一般为3%。 1:允许Overcommit,这种策略适合那些不能承受内存分配失败的应用,比如某些科学计算应用。(本文中涉及的系统就是开启的这个策略) 2:禁止Overcommit,在这个情况下,系统所能分配的内存不会超过下面的CommitLimit,计算方法为Swap + RAM * /proc/sys/vm/overcmmit_ratio,默认50%,可调整),如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序。

2 查看已分配内存

grep -i commit /proc/meminfo

CommitLimit: 6201492 kB # 虚拟内存限定值

Committed_AS: 5770836 kB # 已分配内存,如果大于CommitLimit说明开启了允许Overcommit的策略

3 查看oom相关

# 进程oom得分,0不kill

cat /proc/{pid}/oom_score

# 进程oom调整兼容,计算时一般会以oom_score_adj替换

cat /proc/{pid}/oom_adj

# 用户打分调整。最小值-1000,将会禁止oom killer杀此进程。

#取值从-1000到1000,表示对最终得分的折扣到惩罚。

cat /proc/{pid}/oom_score_adj

参考资料:

<think>我们正在分析一条内核日志,日志显示mapController进程触发了OOM Killer。日志关键信息包括: - gfp_mask=0x24201ca:内存分配标志,表示分配内存时的条件和类型。 - order=0:请求的内存块大小(2^0 * PAGE_SIZE)。 - oom_score_adj=-1000:表示该进程被调整为不太可能被OOM Killer选中(通常为关键进程)。 - 进程名:mapController,运行在未修改的内核4.4.198上。 触发OOM Killer的原因分析: 1. **内存分配失败**: - `mapController`进程尝试分配内存(可能是连续页,因为order=0表示一页)时,系统无法满足该请求。 - 尽管该进程的`oom_score_adj=-1000`(通常意味着被保护),但此时系统内存已经枯竭到必须杀死进程的地步。 2. **系统级内存枯竭**: - 即使有进程被保护,当系统内存严重不足时,OOM Killer仍会被触发。内核会遍历所有进程(包括受保护的进程)并选择一个最优的进程终止。 - 此处,虽然`mapController`的调整分数很低,但可能因为它正在尝试分配内存,所以出现在日志中(表示分配失败触发了OOM Killer),但实际被终止的进程可能不是它(因为分数低,它可能不会被选为牺牲者)。 3. **gfp_mask解析**(0x24201ca): - 分解标志位: - `___GFP_IO` (0x40):允许I/O操作以释放内存。 - `___GFP_FS` (0x80):允许文件系统操作。 - `__GFP_HIGH` (0x80):高优先级请求。 - `GFP_HIGHUSER` (0x200000):从高端内存区域分配,用于用户空间。 - 其他标志:可能包含迁移类型等。 - 这个标志表示一个用户空间的高优先级请求,允许进行I/O和文件系统操作来满足分配。 4. **可能的内存耗尽原因**: - 系统内存被其他进程大量占用(可能是内存泄漏或正常使用)。 - 内存碎片导致无法分配连续页(即使总空闲内存足够,但order=0的分配请求通常不会受碎片影响,除非系统碎片极其严重)。 - 交换空间已满,且文件缓存无法及时释放。 5. **为什么调整分数为-1000的进程触发OOM**: - 注意:触发OOM Killer进程不一定是被杀死的进程。这里日志显示`mapController`在分配内存时失败,因此内核在它的上下文中触发了OOM Killer机制。但实际选择杀死哪个进程是根据所有进程的`oom_score`(由`oom_score_adj`和内存使用量计算得出)决定的。 - 由于`mapController`的`oom_score_adj=-1000`,它的实际分数会很低,因此OOM Killer很可能选择另一个内存占用多且分数高的进程终止。 因此,根本原因在于系统内存耗尽,导致`mapController`进程的内存分配失败,从而触发OOM Killer机制。尽管`mapController`本身受到保护,但系统必须杀死其他进程来释放内存。 §§相关问题§§ 1. 当进程oom_score_adj设置为-1000时,OOM Killer在什么极端情况下仍可能杀死该进程? 2. 如何解读内核日志中的gfp_mask标志(例如0x24201ca)? 3. 内存碎片化如何影响不同order的内存分配? 4. 在OOM Killer被触发后的选择过程中,哪些因素会影响进程的最终oom_score? 5. 为什么日志中显示的是触发OOM Killer进程(mapController)而不是实际被终止的进程? </think>### `mapController`进程触发OOM Killer的原因分析 根据内核日志信息: ``` mapController invoked oom-killer: gfp_mask=0x24201ca, order=0, oom_score_adj=-1000 ``` 该进程触发OOM Killer的核心原因是**内存分配请求失败**,具体机制如下: 1. **内存分配标志解析** `gfp_mask=0x24201ca` 表示: - `GFP_HIGHUSER` (0x200000):用户空间高优先级请求 - `__GFP_FS` (0x80) + `__GFP_IO` (0x40):允许执行文件系统和I/O操作 - `GFP_ZONEMASK` (0x0ca):从常规内存区域分配 - 此组合表明进程尝试分配用户空间内存但系统无法满足 2. **低优先级进程触发异常** `oom_score_adj=-1000` 的特殊含义: - 通常表示**内核线程**(如kworker)或**特权进程** - 负值本应使其免于被OOM Killer终止 - 触发OOM表明: ▶ 系统内存枯竭程度极严重(物理内存+swap完全耗尽) ▶ 内核无法通过常规回收机制释放内存 3. **直接触发链条** ```mermaid graph TD A[mapController申请内存] --> B{检查内存水位} B -->|低于min_free_kbytes| C[直接唤醒OOM Killer] C --> D[跳过内存回收流程] ``` 4. **根本原因推测** - **内存泄漏**:`mapController`自身或关联进程持续占用内存未释放 - **资源冲突**:内核版本4.4.198可能存在已知OOM缺陷 - **特殊场景**:分配order=0(单页)失败表明: - 内存碎片化严重(外部碎片 > 4KB- ZONE_NORMAL区域完全枯竭 --- ### 关键诊断建议 ```bash # 检查进程实际内存画像 grep -B 10 "mapController" /var/log/kern.log # 分析内存碎片 cat /proc/buddyinfo ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值