OOM Killer触发机制与日志定位
Linux内核在物理内存和Swap空间均耗尽时,触发OOM Killer选择进程终止。这不是随机杀进程——内核按oom_score排序,分值最高的进程被选中。分值计算基于进程占用的内存比例:占内存越多、优先级越低(oom_score_adj越高),被杀概率越大。
OOM事件日志有两个来源:
# 方法1:dmesg内核日志
dmesg -T | grep -i "oom\|out of memory\|killed process"
# 方法2:systemd journal
journalctl -k --since "1 hour ago" | grep -i "oom"
# 典型OOM日志输出示例:
# [Tue Jul 30 10:15:23 2026] java invoked oom-killer:
# gfp_mask=0x14000ca(GFP_KERNEL), nodemask=(null)
# [Tue Jul 30 10:15:23 2026] Task java (pid 18423) used 8192MB
# [Tue Jul 30 10:15:23 2026] oom_kill_process+0x14a/0x200
日志会输出被杀进程的PID、占用内存量、触发OOM的进程。先确认是被杀进程本身内存泄漏,还是其他进程把内存吃光导致误杀。
进程级内存占用拆解
定位内存问题先从进程级别入手,搞清楚内存具体花在哪里:
# RSS(常驻物理内存)排序
ps aux --sort=-rss | head -20
# 更精确的方式:smaps统计
cat /proc/$(pidof java)/smaps_rollup
# 输出:Rss:8192000 kB Pss:8100000 kB Shared_Clean:2048 kB
# 按内存类型拆解
pmap -x $(pidof java) | tail -1
# total 8388608 8192000 8192000
RSS包含共享库页面,可能被多个进程重复计算。Pss(Proportional Set Size)按比例分摊共享内存,更准确反映进程的真实内存开销。如果多个Java进程的RSS加起来远超物理内存但Pss合理,说明共享库被多次计入。
Slab缓存泄漏:被忽视的内核级内存黑洞
Slab是内核对象的缓存分配器,正常情况下dentry和inode缓存占用量有限。但某些场景(大量文件遍历、频繁创建删除临时文件)会导致dentry缓存无限膨胀,且不被进程RSS统计——top和ps看不到这部分内存消耗。
# 查看Slab内存总量
cat /proc/meminfo | grep -i slab
# Slab: 4096000 kB
# SReclaimable: 3500000 kB ← 可回收的Slab内存
# SUnreclaim: 596000 kB ← 不可回收
# 按Slab类型拆解
slabtop -o -s c | head -20
# OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
# 524288 524288 100% 192 65536 8 2097152k dentry
# 262144 262144 100% 608 32768 8 2097152k inode_cache
# 强制回收可回收Slab
echo 2 > /proc/sys/vm/drop_caches # 回收dentry和inode缓存
echo 3 > /proc/sys/vm/drop_caches # 回收page cache + dentry + inode
如果Slab中dentry缓存持续增长且drop_caches后迅速反弹,说明有进程在不断创建文件系统元数据。排查方向:用auditd监控文件系统调用,或通过bpftrace抓取路径:
# bpftrace抓取dentry创建来源
bpftrace -e '
kprobe:d_alloc {
printf("%s: %s\n", comm, str(arg1));
}' | sort | uniq -c | sort -rn | head -20
应用层内存泄漏定位
Java应用是最常见的内存泄漏来源。JVM堆外内存泄漏排查比堆内更棘手:
# 1. 堆内存分析
jmap -heap $(pidof java)
jmap -dump:format=b,file=heap.hprof $(pidof java)
# 用MAT或VisualVM分析hprof文件
# 2. 堆外内存排查
# Direct Buffer使用量
jcmd $(pidof java) VM.native_memory summary
# Native内存跟踪(需启动时加-XX:NativeMemoryTracking=summary)
java -XX:NativeMemoryTracking=summary -jar app.jar
jcmd $(pidof java) VM.native_memory baseline
# 运行一段时间后对比
jcmd $(pidof java) VM.native_memory detail.diff
Python应用内存泄漏排查工具链:
# tracemalloc定位泄漏源
import tracemalloc
tracemalloc.start()
# ... 运行业务代码 ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
print(stat)
# 对比两个时间点的内存快照
snapshot2 = tracemalloc.take_snapshot()
diff = snapshot2.compare_to(snapshot, 'lineno')
for stat in diff[:10]:
print(stat)
Cgroups内存限制与OOM防护
对关键服务设置cgroups内存上限,防止单个进程吃光整机内存:
# 创建cgroup并设置内存限制
mkdir -p /sys/fs/cgroup/memory/app_java
echo 4G > /sys/fs/cgroup/memory/app_java/memory.limit_in_bytes
echo $(pidof java) > /sys/fs/cgroup/memory/app_java/tasks
# 设置OOM时暂停而非杀死(配合外部监控拉起)
echo 1 > /sys/fs/cgroup/memory/app_java/memory.oom_control
# 监控cgroup内存使用
cat /sys/fs/cgroup/memory/app_java/memory.usage_in_bytes
cat /sys/fs/cgroup/memory/app_java/memory.failcnt
memory.oom_control设为1后,OOM时进程被冻结而非被杀,外部监控系统检测到进程冻结后可以抓取内存快照再重启,保留现场供后续分析。
内存监控告警方案
主动监控比被动等OOM更可靠。关键监控项:
可用内存水位:MemAvailable低于总内存15%时告警。MemAvailable已计入可回收的Slab和Page Cache,是最准确的可用内存指标。不要用MemFree——它不含可回收缓存,数值偏低会误报。
Swap使用增长趋势:Swap使用量持续增长说明物理内存不足,内核在把匿名页面换出到磁盘。正常运行的机器Swap应该为0或稳定在极低值。
进程RSS增长率:对关键进程监控RSS每小时增量,连续3小时增长超过500MB触发告警。
OOM事件计数:dmesg中oom-killer调用次数,任何新增OOM事件立即告警,级别P1。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/linux-fu-wu-qi-nei-cun-xie-lou-pai-cha-shi-zhan-cong/