Linux服务器内存泄漏排查实战:OOM Killer机制分析与诊断流程

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/

(0)
小编小编
上一篇 15小时前
下一篇 15小时前

相关推荐