Linux OOM 排查完全指南:从原理到实战

本文合并整合:系统介绍 Linux OOM 机制原理、排查方法论与 Envoy 实战案例


一、OOM Killer 原理机制

1.1 什么是 OOM Killer

当系统物理内存和交换空间都被用完时,如果进程继续申请内存,Linux 内核会触发 OOM (Out Of Memory) killer 机制来终止进程释放内存。

1.2 OOM 触发流程

graph TD
    A[进程申请内存] --> B{物理内存+Swap是否耗尽?}
    B -->|否| C[分配内存成功]
    B -->|是| D{panic_on_oom?}
    D -->|=2| E[系统 Panic]
    D -->|=1| F{符合条件?}
    F -->|是| E
    F -->|否| G{oom_kill_allocating_task?}
    G -->|=1| H[Kill 当前进程]
    G -->|=0| I[计算各进程 OOM 分数]
    I --> J[Kill 分数最高进程]
    J --> K{oom_dump_tasks=1?}
    K -->|是| L[生成 core dump]
    K -->|否| M[记录日志]
    E --> N[panic 秒后重启]
    H --> O[内存释放]
    L --> O
    M --> O
graph TD
    A[进程申请内存] --> B{物理内存+Swap是否耗尽?}
    B -->|否| C[分配内存成功]
    B -->|是| D{panic_on_oom?}
    D -->|=2| E[系统 Panic]
    D -->|=1| F{符合条件?}
    F -->|是| E
    F -->|否| G{oom_kill_allocating_task?}
    G -->|=1| H[Kill 当前进程]
    G -->|=0| I[计算各进程 OOM 分数]
    I --> J[Kill 分数最高进程]
    J --> K{oom_dump_tasks=1?}
    K -->|是| L[生成 core dump]
    K -->|否| M[记录日志]
    E --> N[panic 秒后重启]
    H --> O[内存释放]
    L --> O
    M --> O
graph TD
    A[进程申请内存] --> B{物理内存+Swap是否耗尽?}
    B -->|否| C[分配内存成功]
    B -->|是| D{panic_on_oom?}
    D -->|=2| E[系统 Panic]
    D -->|=1| F{符合条件?}
    F -->|是| E
    F -->|否| G{oom_kill_allocating_task?}
    G -->|=1| H[Kill 当前进程]
    G -->|=0| I[计算各进程 OOM 分数]
    I --> J[Kill 分数最高进程]
    J --> K{oom_dump_tasks=1?}
    K -->|是| L[生成 core dump]
    K -->|否| M[记录日志]
    E --> N[panic 秒后重启]
    H --> O[内存释放]
    L --> O
    M --> O

1.3 关键配置参数

参数默认值说明
/proc/sys/vm/panic_on_oom00=不panic, 1=可能panic, 2=一定panic
/proc/sys/vm/oom_kill_allocating_task00=kill最高分进程, 1=kill当前进程
/proc/sys/vm/oom_dump_tasks1是否在 OOM 时 dump 任务信息
/proc/sys/kernel/panic0panic 后多少秒重启系统(0=不重启)

1.4 OOM 分数计算

每个进程的 OOM 分数由内核自动计算,存储在 /proc/<pid>/oom_score

影响因素

  • 进程运行时间(越长越重要,分数越低)
  • 进程已分配内存(越多分数越高)
  • 进程 nice 值
  • 子进程数量
  • CPU 时间

手动调整分数

通过 /proc/<pid>/oom_score_adj 调整(范围 -1000 到 1000):

1
2
3
4
5
# -1000 表示永远不会被 OOM killer 杀掉
echo -1000 > /proc/<pid>/oom_score_adj

# 500 表示提高被 kill 的优先级
echo 500 > /proc/<pid>/oom_score_adj

注意:旧版本使用 oom_adj(-17 到 15),-17 等同于现在的 -1000

1.5 查看系统配置

1
2
3
4
5
6
7
8
# 查看 OOM 配置
cat /proc/sys/vm/panic_on_oom
cat /proc/sys/vm/oom_kill_allocating_task
cat /proc/sys/vm/oom_dump_tasks

# 查看进程 OOM 分数
cat /proc/$(pidof envoy)/oom_score
cat /proc/$(pidof envoy)/oom_score_adj

1.6 修改配置(三种方式)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 方式1:直接写文件(重启失效)
sudo sh -c "echo 0 > /proc/sys/vm/panic_on_oom"

# 方式2:sysctl 命令(重启失效)
sudo sysctl vm.panic_on_oom=0

# 方式3:配置文件(重启生效)
sudo vim /etc/sysctl.conf
# 添加:vm.panic_on_oom=0
sudo sysctl -p  # 立即生效

二、OOM 排查方法论

2.1 快速诊断流程

graph LR
    A[系统异常] --> B{进程被Kill?}
    B -->|是| C[dmesg 查看日志]
    B -->|否| D{内存持续增长?}
    C --> E[确认 OOM]
    D -->|是| F[主动分析]
    D -->|否| G[其他问题]
    E --> H[定位被Kill进程]
    F --> H
    H --> I[分析内存占用]
    I --> J[定位根因]
graph LR
    A[系统异常] --> B{进程被Kill?}
    B -->|是| C[dmesg 查看日志]
    B -->|否| D{内存持续增长?}
    C --> E[确认 OOM]
    D -->|是| F[主动分析]
    D -->|否| G[其他问题]
    E --> H[定位被Kill进程]
    F --> H
    H --> I[分析内存占用]
    I --> J[定位根因]
graph LR
    A[系统异常] --> B{进程被Kill?}
    B -->|是| C[dmesg 查看日志]
    B -->|否| D{内存持续增长?}
    C --> E[确认 OOM]
    D -->|是| F[主动分析]
    D -->|否| G[其他问题]
    E --> H[定位被Kill进程]
    F --> H
    H --> I[分析内存占用]
    I --> J[定位根因]

2.2 常用排查命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 检查系统内存状态
free -h
cat /proc/meminfo

# 2. 查看 OOM 日志
dmesg | grep -i "killed process"
dmesg | grep -i "out of memory"
grep -i "oom" /var/log/messages
journalctl -k | grep -i oom

# 3. 查看进程内存占用(按内存排序)
ps aux --sort=-%mem | head -20
top -o %MEM

# 4. 查看进程详细内存信息
cat /proc/<pid>/status
pmap -x <pid>
smem -s pss

# 5. 查看系统整体内存分布
smem --pie name
slabtop

2.3 OOM 日志分析

典型的 OOM 日志示例:

1
2
3
[  490.006836] eat_memory invoked oom-killer: gfp_mask=0x24280ca, order=0, oom_score_adj=0
[  490.006871]  [<ffffffff81191442>] oom_kill_process+0x202/0x3c0
[  490.007123] Out of memory: Killed process 12345 (envoy) total-vm:1234567kB, anon-rss:987654kB, file-rss:12345kB, shmem-rss:0kB

关键信息

  • gfp_mask:内存分配标志
  • oom_score_adj:进程的 OOM 分数调整值
  • total-vm:总虚拟内存
  • anon-rss:匿名物理内存
  • file-rss:文件映射物理内存

三、深度内存分析

3.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
27
28
29
30
┌─────────────────────────────────────────────────────────┐
│                      进程虚拟地址空间                      │
├─────────────────────────────────────────────────────────┤
│  Kernel Space (高地址)                                   │
├─────────────────────────────────────────────────────────┤
│  Stack (栈)                                              │
├─────────────────────────────────────────────────────────┤
│    ↓                                                    │
│    ↓ (增长方向)                                          │
│    ↓                                                    │
├─────────────────────────────────────────────────────────┤
│  Memory Mapping Segment (mmap 区域)                       │
│  - 动态库加载                                            │
│  - 大内存分配                                            │
│  - 共享内存                                              │
├─────────────────────────────────────────────────────────┤
│    ↑                                                    │
│    ↑ (增长方向)                                          │
│    ↑                                                    │
├─────────────────────────────────────────────────────────┤
│  Heap (堆)                                               │
├─────────────────────────────────────────────────────────┤
│  BSS Segment (未初始化数据)                              │
├─────────────────────────────────────────────────────────┤
│  Data Segment (已初始化数据)                             │
├─────────────────────────────────────────────────────────┤
│  Text Segment (代码段)                                   │
├─────────────────────────────────────────────────────────┤
│  (低地址)                                                │
└─────────────────────────────────────────────────────────┘

3.2 /proc//maps 解读

1
cat /proc/<pid>/maps

输出格式:

1
2
3
4
5
地址范围          权限   偏移   设备    inode        文件路径
55c9acb52000-55c9adbd1000 r--p 00000000 103:03 11953407  /usr/local/bin/envoy
55c9adbd1000-55c9afed8000 r-xp 0107e000 103:03 11953407  /usr/local/bin/envoy
55c9b1f0d000-55c9b500e000 rw-p 00000000 00:00 0          [heap]
7f3fcf214000-7f3fd12f4000 rw-p 00000000 00:00 0          [anon]

权限说明

  • r = read, w = write, x = execute, p = private, s = shared

内存类型

  • [heap]:堆内存
  • [stack]:栈内存
  • [anon]:匿名内存映射(malloc 大块内存)
  • 文件路径:动态库或可执行文件映射

3.3 pmap 详细分析

1
pmap -x <pid>

输出示例:

1
2
Address           Kbytes     RSS   Dirty Mode  Mapping
000055c9b1f0d000   50180   43952   43952 rw---   [ anon ]

字段说明

  • Kbytes:虚拟内存大小
  • RSS (Resident Set Size):实际物理内存占用
  • Dirty:脏页数量(已修改但未写回磁盘)

快速定位大内存块

1
2
# 按排序查看最大 RSS 内存块
pmap -x <pid> | sort -nk 3 | tail -10

3.4 smem 高级分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 安装 smem
sudo apt install smem

# 按 PSS (Proportional Set Size) 排序
smem -s pss -k

# 查看用户内存占用
smem -u -k

# 查看进程内存映射详情
smem -m <pid>

内存指标说明

  • VSS (Virtual Set Size):虚拟内存大小
  • RSS (Resident Set Size):物理内存占用
  • PSS (Proportional Set Size):共享内存按比例分配后的实际占用
  • USS (Unique Set Size):进程独占内存

四、实战案例:Envoy OOM 排查

4.1 问题背景

Envoy 进程持续占用内存增长,最终被 OOM killer 杀死。

4.2 排查步骤

步骤1:确认进程 PID

1
2
ps -ef | grep envoy
# 1337  42947  42820  0 Jan19 ?  00:15:19 /usr/local/bin/envoy ...

步骤2:查看内存布局

1
2
# 快速定位最大内存块
pmap -x 42947 | sort -nk 3 | tail -5

输出:

1
2
3
4
5
Address           Kbytes     RSS   Dirty Mode  Mapping
000055c9b1f0d000   50180   43952   43952 rw---   [ anon ]
00007f3fcf214000   33664   33560   33560 rw---   [ anon ]
---------------- ------- ------- -------
total kB          243060  101508   82704

发现 0x55c9b1f0d000 内存块占用约 50MB,RSS 占总内存近 50%。

步骤3:确认内存块范围

1
2
# 在 maps 中查找该内存块的起止地址
cat /proc/42947/maps | grep 55c9b1f0d000

输出:

1
55c9b1f0d000-55c9b500e000 rw-p 00000000 00:00 0          [heap]

内存块范围:0x55c9b1f0d0000x55c9b500e000

步骤4:Dump 内存块内容

1
gdb --batch --pid 42947 -ex "dump memory stack.dump 0x55c9b1f0d000 0x55c9b500e000"

步骤5:分析内存内容

1
2
3
4
5
# 查看可读字符串
strings stack.dump | less

# 十六进制查看
hexdump -C stack.dump | less

步骤6:完整内存 Dump(可选)

1
2
3
4
5
6
# 生成完整 core dump
gcore 42947

# 或使用 gdb 附加分析
gdb -p 42947
(gdb) generate-core-file envoy.core

4.3 Envoy 专用分析工具

开启 heap profiler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 开启 heap profiler
kubectl exec -n "$ns" "$pod" -c istio-proxy -- \
  curl -X POST "http://localhost:15000/heapprofiler?enable=y"

# 等待 15 秒采集数据
sleep 15

# 关闭 profiler
kubectl exec -n "$ns" "$pod" -c istio-proxy -- \
  curl -X POST "http://localhost:15000/heapprofiler?enable=n"

# 复制 profile 数据
kubectl cp -n "$ns" "$pod":/var/lib/istio/data /tmp/envoy -c istio-proxy

获取 Envoy 配置和状态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 配置 dump
kubectl exec -n "$ns" "$pod" -c istio-proxy -- \
  curl http://127.0.0.1:15000/config_dump

# Cluster 信息
kubectl exec -n "$ns" "$pod" -c istio-proxy -- \
  curl http://127.0.0.1:15000/clusters?format=json

# 内存统计
kubectl exec -n "$ns" "$pod" -c istio-proxy -- \
  curl http://127.0.0.1:15000/memory

4.4 自动化排查脚本

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/bin/bash
# OOM 排查自动化脚本

POD_NAME="$1"
NAMESPACE="$2"

if [ -z "$POD_NAME" ] || [ -z "$NAMESPACE" ]; then
    echo "Usage: $0 <pod-name> <namespace>"
    exit 1
fi

OUTPUT="${POD_NAME}-oom-analysis.log"

echo "=== Envoy OOM Analysis: ${POD_NAME} ===" > ${OUTPUT}
echo "Time: $(date)" >> ${OUTPUT}
echo "" >> ${OUTPUT}

# 1. Envoy config dump
echo "### Config Dump ###" >> ${OUTPUT}
kubectl exec -n ${NAMESPACE} ${POD_NAME} -c istio-proxy -- \
  curl -s http://127.0.0.1:15000/config_dump >> ${OUTPUT} 2>&1

# 2. 获取容器 PID
CONTAINER_ID=$(kubectl get pod -n ${NAMESPACE} ${POD_NAME} \
  -o jsonpath='{.status.containerStatuses[0].containerID}' | awk -F '//' '{print $NF}')

if [ -z "$CONTAINER_ID" ]; then
    echo "Error: Cannot get container ID" >> ${OUTPUT}
    exit 1
fi

PPID=$(docker inspect -f '{{.State.Pid}}' ${CONTAINER_ID})
PID=$(ps -ef | grep ${PPID} | grep envoy | awk '{print $2}')

echo "" >> ${OUTPUT}
echo "### Process Info ###" >> ${OUTPUT}
echo "Container ID: ${CONTAINER_ID}" >> ${OUTPUT}
echo "Pilot-agent PID: ${PPID}" >> ${OUTPUT}
echo "Envoy PID: ${PID}" >> ${OUTPUT}

# 3. 内存映射
echo "" >> ${OUTPUT}
echo "### /proc/${PID}/maps ###" >> ${OUTPUT}
cat /proc/${PID}/maps >> ${OUTPUT} 2>&1

# 4. pmap
echo "" >> ${OUTPUT}
echo "### pmap -x ${PID} ###" >> ${OUTPUT}
pmap -x ${PID} >> ${OUTPUT} 2>&1

# 5. 最大内存块 dump
echo "" >> ${OUTPUT}
echo "### Dumping largest memory block ###" >> ${OUTPUT}
STACK_START=$(pmap -x ${PID} | sort -nk 3 | grep -v total | tail -1 | awk '{print $1}')
STACK_END=$(cat /proc/${PID}/maps | grep ${STACK_START} | awk -F '-' '{print $2}')

echo "Dumping 0x${STACK_START} - 0x${STACK_END}" >> ${OUTPUT}
gdb --batch -p ${PID} \
  -ex "dump memory ${POD_NAME}-memory.dump 0x${STACK_START} 0x${STACK_END}" \
  >> ${OUTPUT} 2>&1

# 6. 分析 dump
echo "" >> ${OUTPUT}
echo "### Memory content preview ###" >> ${OUTPUT}
strings ${POD_NAME}-memory.dump | head -100 >> ${OUTPUT}

echo "" >> ${OUTPUT}
echo "=== Analysis Complete ===" >> ${OUTPUT}
echo "Output: ${OUTPUT}"
echo "Memory dump: ${POD_NAME}-memory.dump"

使用方法:

1
2
chmod +x oom-analysis.sh
./oom-analysis.sh my-pod-xxx my-namespace

五、内存泄漏分析进阶

5.1 使用 gdb 分析 C++ 对象

对于 C++ 程序(如 Envoy),可以统计内存中的对象类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 获取最大内存块地址
STACK_START=$(pmap -x ${PID} | sort -nk 3 | grep -v total | tail -1 | awk '{print $1}')

# 使用 gdb 输出内存内容
gdb -c core.${PID} ${BINARY}
(gdb) set height 0
(gdb) set logging file gdb.txt
(gdb) set logging on
(gdb) x/612205568a 0x${STACK_START}
(gdb) set logging off

# 解析 C++ 符号
cat gdb.txt | c++filt | \
  awk 'BEGIN{stat[""]=0}{stat[$5]++}END{for(i in stat) print i"   "stat[i]}' | \
  sort -nk 2

5.2 使用 Valgrind

1
2
3
4
5
6
7
8
9
# 内存泄漏检测
valgrind --leak-check=full --show-leak-kinds=all \
         --log-file=valgrind.log ./your_program

# Massif 堆分析
valgrind --tool=massif --massif-out-file=massif.out ./your_program

# 分析结果
ms_print massif.out

5.3 使用 AddressSanitizer

编译时添加 ASan:

1
2
gcc -fsanitize=address -g your_program.c -o your_program
./your_program

六、容器环境 OOM

6.1 Kubernetes 中的 OOM

两类 OOM

  1. 系统级 OOM:节点内存耗尽,kubelet 触发
  2. 容器级 OOM:超过容器 memory limit,内核直接 kill

6.2 Kubelet OOM 机制

1
2
3
4
5
6
# Pod 资源限制
resources:
  limits:
    memory: "512Mi"
  requests:
    memory: "256Mi"

当容器超过 limit 时:

1
Memory cgroup out of memory: Kill process 12345 (java) score 900 or sacrifice child

6.3 容器 OOM 排查

1
2
3
4
5
6
7
8
9
# 查看 Pod 事件
kubectl describe pod <pod-name>

# 查看容器日志
kubectl logs <pod-name> --previous

# 进入容器查看
kubectl exec -it <pod-name> -- cat /sys/fs/cgroup/memory/memory.limit_in_bytes
kubectl exec -it <pod-name> -- cat /sys/fs/cgroup/memory/memory.usage_in_bytes

6.4 cgroup v2 OOM

1
2
3
4
5
6
7
# cgroup v2 路径
cat /sys/fs/cgroup/<cgroup-path>/memory.max
cat /sys/fs/cgroup/<cgroup-path>/memory.current
cat /sys/fs/cgroup/<cgroup-path>/memory.oom.group

# 查看 OOM 事件
cat /sys/fs/cgroup/<cgroup-path>/memory.events

七、预防措施与最佳实践

7.1 监控指标

指标告警阈值说明
节点内存使用率>85%预警
容器内存使用率>limit*0.9即将 OOM
OOM Kill 事件>0已发生 OOM
内存持续增长10%/5min可能泄漏

7.2 Prometheus 监控规则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 节点内存告警
- alert: NodeMemoryHigh
  expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 0.85
  for: 5m
  annotations:
    summary: "Node {{ $labels.instance }} memory usage > 85%"

# 容器即将 OOM
- alert: ContainerMemoryNearLimit
  expr: container_memory_usage_bytes{container!="",container!="POD"} > container_spec_memory_limit_bytes * 0.9
  for: 2m
  annotations:
    summary: "Container {{ $labels.container }} near OOM"

# OOM Kill 检测
- alert: OOMKilled
  expr: increase(kube_pod_container_status_terminated_reason{reason="OOMKilled"}[5m]) > 0
  annotations:
    summary: "Pod {{ $labels.pod }} was OOM killed"

7.3 资源配置建议

 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
27
# Java 应用
resources:
  limits:
    memory: "2Gi"
  requests:
    memory: "1Gi"
env:
  - name: JAVA_OPTS
    value: "-Xms1g -Xmx1536m -XX:MaxRAMPercentage=75"

# Go 应用
resources:
  limits:
    memory: "512Mi"
  requests:
    memory: "256Mi"
# Go 运行时会自动根据 cgroup limit 限制内存

# Node.js 应用
resources:
  limits:
    memory: "512Mi"
  requests:
    memory: "256Mi"
env:
  - name: NODE_OPTIONS
    value: "--max-old-space-size=384"

7.4 系统调优

1
2
3
4
5
6
7
8
# /etc/sysctl.conf
vm.overcommit_memory = 0    # 严格 overcommit 检查
vm.overcommit_ratio = 50    # overcommit 比例
vm.panic_on_oom = 0         # 不 panic
vm.swappiness = 10          # 降低 swap 使用倾向

# 应用配置
sudo sysctl -p

7.5 常见陷阱

陷阱说明解决方案
Java 堆外内存堆外内存不受 -Xmx 限制设置 -XX:MaxDirectMemorySize
Go goroutine 泄漏每个 goroutine 占用 2KB 栈使用 pprof 检测
内存碎片化长时间运行导致内存碎片定期重启或使用 jemalloc
Shared libraries多进程共享库重复计算正确理解 PSS
Page Cache文件缓存占用大量内存区分 cache/dirty 内存

八、快速参考

常用命令速查

 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
27
28
# === 系统级检查 ===
free -h                              # 内存概览
cat /proc/meminfo | grep -E "MemTotal|MemFree|Cached|Swap"  # 详细内存
vmstat 1 10                          # 内存/swap 统计

# === OOM 日志 ===
dmesg | grep -i "killed process"     # OOM kill 记录
journalctl -k -b -1 | grep -i oom    # 上次启动的 OOM 日志
grep "Out of memory" /var/log/syslog

# === 进程内存 ===
ps aux --sort=-%mem | head -20       # 按内存排序
smem -s pss -k                       # 按 PSS 排序
top -o %MEM                          # 实时监控

# === 进程详情 ===
cat /proc/<pid>/status               # 进程状态(含VmRSS/VmSize)
pmap -x <pid>                        # 内存映射详情
cat /proc/<pid>/maps                 # 虚拟地址空间

# === 内存分析 ===
gcore <pid>                          # 生成 core dump
gdb --batch --pid <pid> -ex "dump memory mem.dump 0xSTART 0xEND"
strings mem.dump | head              # 查看内存内容

# === Envoy 专用 ===
curl http://localhost:15000/memory   # Envoy 内存统计
curl http://localhost:15000/heapprofiler?enable=y  # 开启 heap profiler

/proc//status 关键字段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Name:      进程名
Pid:       PID
VmPeak:    峰值虚拟内存
VmSize:    当前虚拟内存
VmRSS:     物理内存占用
VmData:    数据段大小
VmStk:     栈大小
VmExe:     代码段大小
VmLib:     动态库大小
Threads:   线程数

参考资源

0%