Linux OOM Killer 故障分析与调优实战

一、问题背景

OOM(Out Of Memory)Killer 是 Linux 内核在系统内存严重不足时的一种自我保护机制。当系统可用内存低于临界阈值时,内核会强制终止占用内存较高的进程,以释放内存资源保证系统继续运行。

在生产环境中,OOM Killer 的触发往往意味着严重的性能问题甚至服务中断。本文结合实际案例,详细介绍 OOM 故障的分析方法、排查流程和调优策略。

二、OOM Killer 工作原理

2.1 触发条件

Linux 内核在以下情况下会触发 OOM Killer:

  1. 内存分配失败:当进程申请内存但系统无法满足时
  2. 可用内存低于阈值watermark 水位线以下的内存不足
  3. 内存碎片化严重:虽然总空闲内存足够,但无法分配连续的大块内存

2.2 评分机制(oom_score)

内核为每个进程计算 oom_score,分数越高越容易被杀死:

1
2
3
4
5
# 查看进程的 OOM 评分
cat /proc/<pid>/oom_score

# 查看 OOM 评分调整值
cat /proc/<pid>/oom_score_adj

评分计算因素:

  • 进程内存占用量(主要因素)
  • 进程运行时间
  • 进程权限(root 进程有保护)
  • oom_score_adj 手动调整值(范围 -1000 到 1000)

2.3 内核日志特征

OOM 发生时,内核会输出类似日志:

1
2
3
[12345.678901] Out of memory: Kill process 1234 (java) score 850 or sacrifice child
[12345.678902] Killed process 1234 (java) total-vm:8388608kB, anon-rss:6291456kB
[12345.678903] oom_reaper: reaped process 1234 (java), now anon-rss:0kB

关键字段说明:

  • score:进程的 OOM 评分
  • total-vm:虚拟内存总量
  • anon-rss:匿名驻留集大小(实际物理内存占用)

三、故障排查流程

3.1 确认 OOM 发生

1
2
3
4
5
6
7
8
9
10
# 方法 1:查看内核日志
dmesg -T | grep -i "out of memory"
dmesg -T | grep -i "oom-killer"

# 方法 2:查看系统日志
grep -i "out of memory" /var/log/messages
grep -i "oom" /var/log/kern.log

# 方法 3:使用 journalctl(systemd 系统)
journalctl -k | grep -i "oom"

3.2 分析被杀进程

1
2
3
4
5
# 提取 OOM 相关日志
dmesg -T | grep -A 20 "Out of memory" > /tmp/oom_analysis.log

# 查看历史 OOM 记录(如有)
cat /var/log/kern.log.1 | grep -i oom

3.3 检查内存状态

1
2
3
4
5
6
7
8
9
10
11
# 查看内存整体使用情况
free -h

# 查看详细内存信息
cat /proc/meminfo

# 查看各进程内存占用(按 RSS 排序)
ps aux --sort=-%mem | head -20

# 使用 top 实时监控
top -o %MEM

3.4 分析内存消耗来源

1
2
3
4
5
6
7
8
9
10
# 查看各用户内存占用
ps aux | awk '{user[$2]+=$6} END {for (i in user) print user[i], i}' | sort -rn | head

# 查看 cgroup 内存限制(容器环境)
cat /sys/fs/cgroup/memory/memory.limit_in_bytes
cat /sys/fs/cgroup/memory/memory.usage_in_bytes

# 检查 swap 使用情况
swapon --show
cat /proc/swaps

四、常见 OOM 场景分析

4.1 Java 应用 OOM

场景特征

  • Java 进程频繁被杀
  • JVM 堆内存配置过大
  • 存在内存泄漏

排查方法

1
2
3
4
5
6
7
8
# 查看 JVM 内存配置
ps -ef | grep java | grep -E "Xmx|Xms"

# 查看 Java 进程实际内存
jstat -gc <pid> 1000

# 生成堆转储分析(如条件允许)
jmap -dump:format=b,file=heap.hprof <pid>

解决方案

  1. 合理设置 JVM 堆大小(建议不超过物理内存的 50%)
  2. 启用 G1 垃圾收集器:-XX:+UseG1GC
  3. 添加堆转储参数:-XX:+HeapDumpOnOutOfMemoryError
  4. 调整 OOM 评分:echo -500 > /proc/<pid>/oom_score_adj

4.2 容器环境 OOM

场景特征

  • 容器内进程被杀但宿主机内存充足
  • Kubernetes Pod 频繁重启(OOMKilled)

排查方法

1
2
3
4
5
6
7
8
9
# Docker 容器
docker inspect <container_id> | grep -A 10 Memory

# Kubernetes Pod
kubectl describe pod <pod_name> | grep -A 5 "Last State"
kubectl get pod <pod_name> -o jsonpath='{.status.containerStatuses[*].lastState}'

# 查看容器 OOM 记录
dmesg | grep -i "killed process" | grep docker

解决方案

  1. 合理设置容器内存限制(limits)
  2. 设置 requests 保证资源预留
  3. 在 Kubernetes 中配置 QoS 等级
  4. 启用内存监控告警

4.3 数据库服务 OOM

场景特征

  • MySQL/PostgreSQL 进程被杀
  • 查询缓存或缓冲池配置过大
  • 并发连接数过多

解决方案

1
2
3
4
5
-- MySQL 内存相关参数优化
-- 在 my.cnf 中调整:
innodb_buffer_pool_size = 物理内存 * 0.5
max_connections = 合理值(避免过多连接消耗内存)
table_open_cache = 适度值

4.4 系统服务 OOM

场景特征

  • 多个服务同时竞争内存
  • 系统预留内存不足

解决方案

  1. 配置 vm.min_free_kbytes 保留足够系统内存
  2. 使用 cgroups 限制服务内存
  3. 配置 swap 作为缓冲(但注意性能影响)

五、OOM 调优策略

5.1 调整 OOM 评分

保护关键进程不被杀死:

1
2
3
4
5
6
7
8
9
10
11
# 临时调整(重启失效)
echo -900 > /proc/<pid>/oom_score_adj

# 永久调整(通过 systemd)
# 编辑 /etc/systemd/system/<service>.service.d/override.conf
[Service]
OOMScoreAdjust=-900

# 重新加载配置
systemctl daemon-reload
systemctl restart <service>

5.2 内核参数调优

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# /etc/sysctl.conf 添加以下配置

# 调整 OOM Killer 行为(0=启用,1=禁用)
vm.panic_on_oom = 0

# 内存不足时是否调用 OOM Killer
vm.oom_kill_allocating_task = 0

# 最小空闲内存(单位 KB,根据物理内存调整)
vm.min_free_kbytes = 131072

# 过度使用内存的策略
# 0=禁止过度使用,1=智能过度使用,2=总是过度使用
vm.overcommit_memory = 1

# 过度使用比率
vm.overcommit_ratio = 50

应用配置:

1
sysctl -p

5.3 配置 Swap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看 swap 状态
free -h
swapon --show

# 创建 swap 文件(如需要)
dd if=/dev/zero of=/swapfile bs=1G count=4
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

# 永久生效(/etc/fstab)
/swapfile none swap sw 0 0

# 调整 swappiness(默认 60,降低可减少 swap 使用)
echo 10 > /proc/sys/vm/swappiness
echo "vm.swappiness=10" >> /etc/sysctl.conf

5.4 使用 cgroups 限制内存

1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建 cgroup
sudo cgcreate -g memory:/app_limit

# 设置内存限制(2GB)
echo 2147483648 > /sys/fs/cgroup/memory/app_limit/memory.limit_in_bytes

# 将进程加入 cgroup
echo <pid> > /sys/fs/cgroup/memory/app_limit/cgroup.procs

# 或使用 systemd 管理
# 编辑服务文件添加:
[Service]
MemoryLimit=2G

六、监控与告警

6.1 实时监控脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
# oom_monitor.sh

LOG_FILE="/var/log/oom_monitor.log"
THRESHOLD=90 # 内存使用率阈值

check_memory() {
local used_percent=$(free | awk '/Mem:/ {printf "%.0f", $3/$2 * 100}')

if [ $used_percent -gt $THRESHOLD ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') WARNING: Memory usage at ${used_percent}%" >> $LOG_FILE

# 获取内存占用前 5 的进程
echo "Top memory consumers:" >> $LOG_FILE
ps aux --sort=-%mem | head -6 >> $LOG_FILE
echo "---" >> $LOG_FILE
fi
}

check_oom() {
local oom_count=$(dmesg | grep -c "Out of memory")
echo "$(date '+%Y-%m-%d %H:%M:%S') Total OOM events: $oom_count" >> $LOG_FILE
}

check_memory
check_oom

6.2 Prometheus 监控指标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Prometheus 告警规则
groups:
- name: memory_alerts
rules:
- alert: HighMemoryUsage
expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100 > 90
for: 5m
labels:
severity: warning
annotations:
summary: "高内存使用率"
description: "服务器 {{ $labels.instance }} 内存使用率超过 90%"

- alert: OOMKillerDetected
expr: increase(node_vmstat_oom_kill[1h]) > 0
labels:
severity: critical
annotations:
summary: "检测到 OOM Killer 触发"
description: "服务器 {{ $labels.instance }} 在过去 1 小时内触发了 OOM Killer"

6.3 Grafana 仪表盘

关键监控面板:

  1. 内存使用率趋势图
  2. 各进程内存占用排名
  3. OOM 事件时间线
  4. Swap 使用情况

七、实战案例

案例 1:Java 服务频繁 OOM

问题现象
某 Java 微服务每天凌晨 3 点左右被 OOM Killer 杀死,导致业务中断。

排查过程

  1. 查看日志发现 OOM 时 Java 进程占用 6GB 内存
  2. 检查 JVM 参数:-Xmx8g 设置过大
  3. 服务器总内存 8GB,系统和其他服务需要 2GB

解决方案

  1. 调整 JVM 堆大小:-Xmx4g -Xms2g
  2. 启用 G1 GC:-XX:+UseG1GC -XX:MaxGCPauseMillis=200
  3. 添加监控:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/java/
  4. 设置 OOM 保护:OOMScoreAdjust=-500

效果:服务稳定运行,未再出现 OOM。

案例 2:MySQL 服务被杀

问题现象
MySQL 服务在业务高峰期频繁被 OOM Killer 终止。

排查过程

  1. 检查 my.cnf 发现 innodb_buffer_pool_size=6G
  2. 服务器物理内存 8GB
  3. 系统和其他应用需要约 2GB

解决方案

1
2
3
4
5
6
7
8
9
# 调整 MySQL 配置
innodb_buffer_pool_size = 4G
max_connections = 200
table_open_cache = 2000

# 添加 systemd OOM 保护
# /etc/systemd/system/mysqld.service.d/override.conf
[Service]
OOMScoreAdjust=-800

效果:MySQL 服务稳定,系统整体内存使用合理。

八、最佳实践总结

8.1 预防措施

  1. 容量规划:预留 20-30% 内存给系统和突发流量
  2. 合理配置:应用内存配置不超过物理内存的 50-70%
  3. 监控告警:设置 80% 预警、90% 告警阈值
  4. 定期巡检:检查内存泄漏和异常增长

8.2 应急响应

  1. 快速恢复:优先恢复关键业务服务
  2. 保留现场:保存 OOM 日志和堆转储(如可能)
  3. 根因分析:分析是被攻击、泄漏还是配置问题
  4. 持续优化:根据分析结果调整配置和架构

8.3 文档记录

建立 OOM 事件台账,记录:

  • 发生时间
  • 被杀进程
  • 内存状态
  • 根本原因
  • 解决措施
  • 预防方案

九、参考命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# OOM 日志查询
dmesg -T | grep -i "out of memory"
journalctl -k | grep -i oom

# 进程 OOM 评分
cat /proc/<pid>/oom_score
cat /proc/<pid>/oom_score_adj

# 内存状态
free -h
cat /proc/meminfo
vmstat 1 5

# 进程内存排行
ps aux --sort=-%mem | head -20
top -o %MEM

# 调整 OOM 评分
echo -500 > /proc/<pid>/oom_score_adj

# 内核参数
sysctl vm.min_free_kbytes
sysctl vm.overcommit_memory
sysctl vm.swappiness

十、总结

OOM Killer 是 Linux 系统的最后一道防线,但频繁触发说明系统存在严重问题。通过合理的容量规划、配置优化和监控告警,可以有效预防 OOM 事件的发生。当 OOM 发生时,应快速定位根因,采取针对性措施,并建立长效机制防止复发。


文档版本:v1.0
最后更新:2026-03-13
适用系统:Linux(CentOS/Ubuntu/Debian 等)