您的位置:首页 >Linux内存管理实战:OOMKiller触发机制与内存泄漏定位
发布于2026-08-13 阅读(0)
扫一扫,手机访问
线上服务跑着跑着突然没了,dmesg 里再补上一句 Out of memory: Killed process xxxx,这种场面,运维和后端开发基本都不陌生。不过,OOM Killer 并不是“看谁不顺眼就杀谁”,它背后有一套明确的判定逻辑;内存泄漏也不是凭空发生、毫无线索,从 /proc/meminfo 到 eBPF,整条排查工具链其实是完整的。接下来这篇内容,会把内存管理的排查路径从内核机制一路讲到工程实践,尽量讲透、讲明白。

OOM Killer 不是随机杀进程,而是通过 oom_score 给每个进程打分,分数最高的被杀。理解打分逻辑,才能预判哪个进程会被杀、怎么保护关键进程。
# 查看进程的 oom_score
cat /proc//oom_score
# 查看 oom_score_adj(可手动调整,范围 -1000 到 1000)
cat /proc//oom_score_adj oom_score 的主要构成:
核心公式简化理解:
oom_score ≈ (RSS + swap + 页表) 归一化到 0-1000
oom_score += oom_score_adj实际内核实现比这复杂(涉及 oom_badness 函数),但工程上这样理解足够。
# 保护 SSH 守护进程(防止 OOM 后无法登录)
echo -1000 > /proc/$(pidof sshd)/oom_score_adj
# 保护数据库主进程
echo -1000 > /proc/$(pidof mysqld)/oom_score_adj持久化配置(systemd 服务):
# /etc/systemd/system/mysqld.service.d/override.conf
[Service]
OOMScoreAdjust=-1000# 创建 override 后重载
systemctl daemon-reload
systemctl restart mysqld注意:OOMScoreAdjust=-1000 不等于"绝对安全"——如果系统所有进程都被设为 -1000,内核仍会杀分数最高的。这个值是相对偏移,不是绝对保护。
线上 OOM 不只有一种,混淆场景会导致排查方向完全错误。
整个物理内存+swap 耗尽,内核触发全局 OOM Killer。
# 确认系统级 OOM
dmesg -T | grep -i "out of memory"
# 或
journalctl -k | grep -i "oom"典型输出:
[2026-07-31 10:23:45] Out of memory: Killed process 5678 (ja va) total-vm:8G, anon-rss:6G, file-rss:200Mcgroup 内存限制触发,只杀该 cgroup 内的进程,不影响系统其他进程。
# 确认 cgroup OOM
dmesg -T | grep -i "memory cgroup out of memory"典型输出:
[2026-07-31 10:24:01] memory cgroup out of memory: Killed process 9012 (python) total-vm:2G, anon-rss:1.5G进程申请内存失败,自己崩溃。这不是内核杀的,是进程自己处理不了 ENOMEM。
# 检查应用日志
grep -i "cannot allocate memory|ENOMEM|malloc failed" /var/log/app/*.logOOM 发生后的第一件事是定位"谁在吃内存"。工具链从粗到细:
# 关键字段
grep -E "MemTotal|MemFree|MemA vailable|Buffers|Cached|SwapTotal|SwapFree|Slab|SReclaimable|SUnreclaim" /proc/meminfo字段解读:
注意:MemFree 低不代表内存不足。MemA vailable 才是真实可用量。很多人看到 MemFree 只有 200MB 就慌了,实际 Cached 有 10GB 可以随时回收。
# 安装 smem
yum install -y smem # CentOS
apt install -y smem # Ubuntu
# 按PSS排序显示前20个进程
smem -rs pss | head -20
# PSS(Proportional Set Size)比RSS更准确
# 它把共享内存按比例分摊给每个进程RSS vs PSS vs USS 对比:
# pmap 看进程内存映射详情
pmap -x | sort -k3 -n -r | head -20
# 输出示例:
# Address Kbytes RSS Dirty Mode Mapping
# 00007f8a12340000 524288 524288 4096 rw--- [anon]
# 00007f8a34560000 32768 32768 0 r-x-- libc-2.17.so [anon] 段持续增长通常就是泄漏点——匿名内存是 malloc 分配的堆内存。
# 查看内核 Slab 缓存占用
slabtop -o -s c | head -20
# 输出示例:
# Active / Total Objects (% used) : 12345678 / 15000000 (82.3%)
# Active / Total Slabs (% used) : 234567 / 234567 (100.0%)
# Active / Total Caches (% used) : 89 / 120 (74.2%)
#
# OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
# 5678901 5678901 100% 0.19K 135212 42 540848K dentry
# 1234567 1234567 100% 0.66K 22222 55 355552K inode_cachedentry 和 inode_cache 高是正常的(文件系统缓存)。如果某个不认识的 cache 持续增长,可能是内核模块泄漏。
传统工具只能看"现在占了多少",eBPF 能看"谁在分配、分配了多少次"。
# 安装 bpftrace
yum install -y bpftrace
# 追踪 libc malloc 调用,统计每个进程的分配次数和总大小
bpftrace -e '
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc {
@bytes[comm] = @bytes[comm] + arg0;
@calls[comm] = @calls[comm] + 1;
}
interval:s:5 { print(@bytes); print(@calls); clear(@bytes); clear(@calls); }
'# 追踪 __alloc_pages,统计每个进程的页分配
bpftrace -e '
kprobe:__alloc_pages_nodemask {
@alloc[comm] = @alloc[comm] + 1;
}
kprobe:__free_pages {
@free[comm] = @free[comm] + 1;
}
interval:s:10 {
printf("=== Page alloc/free stats ===n");
print(@alloc);
print(@free);
}
'如果某进程 @alloc 远大于 @free,就是泄漏点。
# 堆扩展(brk)和大块分配(mmap)分别追踪
bpftrace -e '
tracepoint:syscalls:sys_enter_brk {
@brk[comm] = @brk[comm] + 1;
}
tracepoint:syscalls:sys_enter_mmap {
@mmap[comm] = @mmap[comm] + 1;
}
interval:s:10 { print(@brk); print(@mmap); }
'线上服务应该用 cgroup v2 限制内存,避免单服务拖垮整机。
# 查看 cgroup 版本
mount | grep cgroup
# cgroup2 on /sys/fs/cgroup type cgroup2 ...
# 查看 service 的 cgroup 路径
systemctl show mysqld --property=ControlGroup
# 设置内存上限(2GB)
systemctl set-property mysqld MemoryMax=2G
interval:s:10 {
printf("=== Page alloc/free stats ===n");
print(@alloc);
print(@free);
}
'如果某进程 @alloc 远大于 @free,就是泄漏点。
# 堆扩展(brk)和大块分配(mmap)分别追踪
bpftrace -e '
tracepoint:syscalls:sys_enter_brk {
@brk[comm] = @brk[comm] + 1;
}
tracepoint:syscalls:sys_enter_mmap {
@mmap[comm] = @mmap[comm] + 1;
}
interval:s:10 { print(@brk); print(@mmap); }
'线上服务应该用 cgroup v2 限制内存,避免单服务拖垮整机。
# 查看 cgroup 版本
mount | grep cgroup
# cgroup2 on /sys/fs/cgroup type cgroup2 ...
# 查看 service 的 cgroup 路径
systemctl show mysqld --property=ControlGroup
# 设置内存上限(2GB)
systemctl set-property mysqld MemoryMax=2G
# 设置内存软限制(超过时开始回收,1.5GB)
systemctl set-property mysqld MemoryHigh=1500M# cgroup v2 内存文件
cat /sys/fs/cgroup/system.slice/mysqld.service/memory.current # 当前使用
cat /sys/fs/cgroup/system.slice/mysqld.service/memory.max # 限制
cat /sys/fs/cgroup/system.slice/mysqld.service/memory.events # 事件计数器memory.events 字段:
# 如果 oom_kill 持续增长,说明 cgroup 限制太低
cat /sys/fs/cgroup/system.slice/mysqld.service/memory.events
# oom 12
# oom_kill 3
# ...#!/bin/bash
# /usr/local/bin/cgroup_mem_alert.sh
# 检查所有 service 的 cgroup 内存使用率
THRESHOLD=90 # 百分比
for cg_dir in /sys/fs/cgroup/system.slice/*/; do
service_name=$(basename "$cg_dir" .service)
current=$(cat "$cg_dir/memory.current" 2>/dev/null)
max=$(cat "$cg_dir/memory.max" 2>/dev/null)
[ "$max" = "max" ] && continue # 未设置限制
[ -z "$current" ] && continue
usage=$((current * 100 / max))
if [ "$usage" -gt "$THRESHOLD" ]; then
echo "[ALERT] $service_name 内存使用 ${usage}% (${current}/${max})"
fi
done# 加入 cron,每 5 分钟检查
echo "*/5 * * * * /usr/local/bin/cgroup_mem_alert.sh >> /var/log/cgroup_mem_alert.log" | crontab -不同语言的泄漏特征不同,排查工具也不同。
# 查看 JVM 堆使用
jmap -heap
# 导出堆 dump
jmap -dump:format=b,file=/tmp/heap.hprof
# 用 MAT (Memory Analyzer Tool) 分析
# 或用 jhat(命令行)
jhat -port 7000 /tmp/heap.hprofJa va 泄漏常见模式:
# 启用 tracemalloc
python3 -c "
import tracemalloc
tracemalloc.start()
# ... 业务代码 ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
print(stat)
"Python 泄漏常见模式:
Go 有 GC 但仍会泄漏——goroutine 泄漏和 channel 阻塞是主要类型。
# 查看 goroutine 数量
curl http://localhost:6060/debug/pprof/goroutine?debug=1 | head -50
# 查看 heap profile
curl http://localhost:6060/debug/pprof/heap?debug=1 > heap.out
go tool pprof heap.out
# (pprof) top 10
# (pprof) list Go 泄漏常见模式:
Prometheus 告警规则:
groups:
- name: memory
rules:
- alert: HostMemoryUsageHigh
expr: (1 - node_memory_MemA vailable_bytes / node_memory_MemTotal_bytes) * 100 > 85
for: 10m
labels:
severity: warning
annotations:
summary: "内存使用率超过 85%"
- alert: HostSwapUsageHigh
expr: (1 - node_memory_SwapFree_bytes / node_memory_SwapTotal_bytes) * 100 > 50
for: 5m
labels:
severity: warning
annotations:
summary: "Swap 使用率超过 50%"
- alert: ProcessOomKilled
expr: increase(node_vmstat_oom_kill[5m]) > 0
labels:
severity: critical
annotations:
summary: "系统在 5 分钟内发生了 OOM Kill"误区一:MemFree 低就加内存
MemFree 低但 MemA vailable 高,说明内存被缓存占了,这是正常行为。加内存解决不了问题,应该看业务是否真的需要那么多内存。
误区二:OOM 杀进程就是内存泄漏
单次 OOM 可能是突发流量、批量任务、配置错误(如 JVM 堆设太大)。只有 OOM 反复发生且进程重启后又快速被杀,才是泄漏。
误区三:关闭 swap 提升性能
关闭 swap 会让 OOM 更激进——内核没有 swap 作为缓冲区,一旦物理内存满就立即杀进程。线上服务建议保留 swap(哪怕很小),作为 OOM 的缓冲。
误区四:cgroup 限制设得太紧
cgroup MemoryMax 设得太紧会导致频繁 OOM Kill,设得太松失去隔离效果。建议初始设为业务峰值 P99 的 1.5 倍,观察一周后调整。
发现内存问题,按这个顺序排查:
dmesg -T | grep -i oom 确认是否 OOM,区分系统级/cgroup级/proc/meminfo 看系统级内存分布smem -rs pss 定位占内存最多的进程pmap -x 看进程内存映射,找 [anon] 增长slabtop 看内核 Slab 是否异常bpftrace 追踪分配热点内存问题排查,核心就两步:先把层级定位清楚,再选择合适的工具。系统级先看 /proc/meminfo,进程级看 smem/pmap,内核级看 slabtop,代码级就交给语言 profiler。cgroup v2 是线上服务做内存隔离的基础设施,每条服务都应该配上。至于 OOM Killer,它的行为其实是可预测、可保护的,千万别把它理解成随机事件。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
4
5
6
7
8
9