OOM Killer触发条件与选择算法
Linux服务器运维中,OOM(Out Of Memory)Kill是最让人头疼的问题之一。当系统可用内存低于min水线,且所有回收手段(kswapd、direct reclaim)都无法释放足够内存时,内核会调用OOM Killer选择一个进程终止,释放内存保住系统。
触发OOM的完整链路:
1. 进程申请内存,触发page fault
2. 内存分配器发现free pages不足,进入slowpath
3. 唤醒kswapd进行后台回收,如果仍然不够则direct reclaim
4. 回收后仍然无法满足分配请求,调用out_of_memory()
5. OOM Killer遍历所有进程,计算oom_score,选择得分最高的进程kill
OOM Killer的选择算法基于oom_badness()函数,核心逻辑是给每个进程打分:
// 简化的oom_badness计算
score = (进程已用内存页数) × 10 / (总系统内存页数)
score += (进程已用swap页数) × 10 / (总系统swap页数)
// 如果进程有oom_score_adj调整,叠加调整值
final_score = score + oom_score_adj
oom_score_adj的范围是-1000到1000。设为-1000的进程永远不会被OOM Kill(内核线程和init除外),设为1000则优先被杀。
内存泄漏的典型表现与初步判断
服务器故障排查的第一步是确认是不是真的内存泄漏。几个判断依据:
1. RSS持续增长不回落:ps或top观察进程RSS,正常服务在负载波动后RSS应该稳定在某个范围,如果只涨不降就是泄漏
2. kswapd持续活跃:cat /proc/vmstat | grep kswapd,如果kswapd_steal持续增长,说明内存压力一直存在
3. Slab缓存异常增长:slabtop观察各slab对象数量,dentry/inode缓存异常膨胀常见于文件操作密集型服务
4. OOM日志出现:dmesg中搜”Out of memory”或”Killed process”
一个快速排查脚本:
#!/bin/bash
# 内存快照对比脚本,每10秒采样一次
echo "Time, PID, Comm, RSS_MB, Swap_MB, oom_score_adj"
while true; do
timestamp=$(date +%H:%M:%S)
for pid in $(ls /proc/ | grep -E '^[0-9]+$'); do
if [ -f /proc/$pid/statm ]; then
rss=$(awk '{print $2}' /proc/$pid/statm)
rss_mb=$((rss * 4 / 1024)) # pages to MB (4K pages)
comm=$(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ' | cut -c1-50)
adj=$(cat /proc/$pid/oom_score_adj 2>/dev/null)
if [ $rss_mb -gt 100 ]; then
echo "$timestamp, $pid, $comm, $rss_mb, $adj"
fi
fi
done
sleep 10
done
Java应用内存泄漏的诊断方法
Java服务器的内存泄漏排查分两步:先确认是堆内还是堆外泄漏。
堆内泄漏用jmap + MAT分析:
# 1. 获取Java进程PID
pid=$(jps -l | grep 'your-app' | awk '{print $1}')
# 2. 生成堆dump(会触发Full GC,生产环境慎用)
jmap -dump:format=b,file=/tmp/heap_${pid}.hprof $pid
# 3. 或者用jcmd(更安全,推荐)
jcmd $pid GC.heap_dump /tmp/heap_${pid}.hprof
# 4. 查看存活对象直方图(不需要dump整个堆)
jcmd $pid GC.class_histogram | head -30
如果class_histogram中某个类的实例数异常多(比如HashMap$Node有数百万个),大概率是集合类未清理导致的泄漏。
堆外泄漏更隐蔽,常见原因:
1. DirectByteBuffer:NIO框架使用堆外内存,-XX:MaxDirectMemorySize默认等于-Xmx值,没有限制时可能无限增长
2. JNI分配:Native库(如Netty的Unsafe操作)直接malloc,不经过JVM管理
3. Metaspace:动态代理、CGLIB生成的类不断加载,Metaspace持续膨胀
排查堆外泄漏:
# 查看DirectByteBuffer使用量
jcmd $pid VM.native_memory summary
# 如果启用了NativeMemoryTracking(启动时加-XX:NativeMemoryTracking=summary)
# 输出会按类别展示内存使用:
# - Total: reserved=4096MB, committed=512MB
# - Java Heap: reserved=2048MB, committed=512MB
# - Internal: reserved=1024MB, committed=256MB <-- 关注这一项
C/C++程序的内存泄漏定位
C/C++服务器程序(如Nginx模块、自研网关)的内存泄漏定位依赖Valgrind或AddressSanitizer。
生产环境推荐用tcmalloc的heap profiler,对性能影响最小:
# 1. 编译时链接tcmalloc
gcc -ltcmalloc app.c -o app
# 2. 运行时设置环境变量
export TCMALLOC_SAMPLE_PARAMETER=524288 # 每512KB采样一次
export HEAPPROFILE=/tmp/heap_profile
# 3. 分析profile文件
pprof --svg /path/to/app /tmp/heap_profile.0001.heap > leak.svg
如果是无法重新编译的二进制,可以用LD_PRELOAD注入:
LD_PRELOAD=/usr/lib/libtcmalloc.so ./app
AddressSanitizer(ASan)适合开发测试阶段,编译时加-fsanitize=address即可。ASan能在泄漏发生时精确打印分配栈,但性能损耗约2x,生产环境不推荐。
内核级内存泄漏的识别与报告
当用户态进程全部排查完毕,内存仍在增长,就要怀疑内核泄漏。内核泄漏的特征是Slab占用异常高且不受vm.drop_caches控制。
# 查看各Slab对象占用
slabtop -o -s c | head -20
# 关注这些常见的泄漏对象:
# - dentry: 文件系统目录项缓存
# - inode: 文件索引节点缓存
# - buffer_head: 块设备缓冲区
# - kmalloc-xxx: 通用内存分配
# 如果dentry数量异常大(千万级),尝试强制回收:
echo 2 > /proc/sys/vm/drop_caches
# 回收后如果dentry数量迅速反弹回来,说明有内核泄漏
watch -n1 "cat /proc/slabinfo | grep dentry"
内核泄漏最终需要通过/proc/slabinfo和ftrace定位。如果确认是内核bug,需要升级内核版本或在社区报告。
OOM防护策略与服务器安全加固
预防OOM比排查OOM更重要。几项关键措施:
1. 设置合理的oom_score_adj:
# 保护SSH和系统关键进程
echo -1000 > /proc/$(pidof sshd)/oom_score_adj
# 数据库进程设为较低优先级(但不保护,避免整体系统挂掉)
echo -500 > /proc/$(pidof mysqld)/oom_score_adj
# 非核心业务进程适当提高优先级
echo 500 > /proc/$(pidof log-agent)/oom_score_adj
2. 配置earlyoom:在OOM Killer触发前提前干预,给运维反应时间:
# 安装earlyoom
apt install earlyoom # Debian/Ubuntu
yum install earlyoom # CentOS
# 配置:当可用内存低于10%或可用swap低于10%时触发
# /etc/default/earlyoom
EARLYOOM_ARGS="-r 3600 -m 10 -s 10"
3. cgroup v2内存限制:用cgroup限制各服务的内存上限,避免单个进程吃光所有内存:
# 创建cgroup并设置内存限制
mkdir /sys/fs/cgroup/app-group
echo 4G > /sys/fs/cgroup/app-group/memory.max
echo 3G > /sys/fs/cgroup/app-group/memory.high # 超过此值开始回收
# 将进程加入cgroup
echo $pid > /sys/fs/cgroup/app-group/cgroup.procs
4. 告警阈值:在监控告警体系中配置内存使用率告警,85% warning、92% critical,留出足够的处理窗口。
紧急处理流程
当服务器已出现OOM征兆(kswapd持续运行、内存使用率超过95%)时:
1. 立即检查dmesg -T | grep -i oom确认是否已触发OOM
2. 如果核心进程被杀,先检查其oom_score_adj是否已正确设置
3. 临时释放内存:echo 3 > /proc/sys/vm/drop_caches(仅释放page cache,不影响业务)
4. 分析内存占用最大的进程,判断是否可以重启
5. 如果是可重启的非核心服务,先kill释放内存保住核心业务
6. 事后必须排查根因,OOM只是症状,不是病因
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/linux-fu-wu-qi-nei-cun-xie-lou-pai-cha-shi-zhan-oomkiller/