20-OOM Killer 机制详解
学习目标
- 深入理解 Linux OOM Killer 的触发条件和工作原理
- 掌握 oom_score 的计算方法和内核源码
- 理解 Cgroup OOM 与系统 OOM 的区别
- 掌握 OOM 防护策略和问题排查方法
- 能够分析和预防生产环境中的 OOM 问题
前置知识
- Linux 内存管理基础
- Cgroup 基本概念
- 基本的 C 语言阅读能力
一、OOM Killer 概述
1.1 什么是 OOM Killer?
OOM (Out of Memory) Killer 是 Linux 内核的一个保护机制,当系统内存严重不足且无法通过正常回收获得足够内存时,内核会选择杀死一个或多个进程来释放内存,防止系统崩溃。
┌────────────────────────────────────────────────────────────────────────┐
│ OOM Killer 触发场景 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ 正常情况: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 进程请求内存 → 分配成功 → 继续运行 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 内存紧张: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 进程请求内存 → 触发回收 → Page Cache/Swap → 分配成功 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ OOM 情况: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 进程请求内存 → 触发回收 → 回收失败 → OOM Killer → 杀死进程 │ │
│ │ ↓ │ │
│ │ 释放内存 → 分配成功 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
1.2 OOM 类型
┌────────────────────────────────────────────────────────────────────────┐
│ OOM 类型对比 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 系统级 OOM (Global OOM) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ - 整个系统物理内存耗尽 │ │
│ │ - Swap 也已用尽或未配置 │ │
│ │ - 从所有进程中选择 victim │ │
│ │ - 日志: "Out of memory: Kill process..." │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. Cgroup OOM (Container OOM) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ - Cgroup 内存使用达到 memory.limit │ │
│ │ - 系统可能还有大量空闲内存 │ │
│ │ - 只从 Cgroup 内的进程中选择 victim │ │
│ │ - 日志: "Memory cgroup out of memory: Kill process..." │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. NUMA OOM │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ - 特定 NUMA 节点内存耗尽 │ │
│ │ - 其他节点可能有空闲内存 │ │
│ │ - 与 cpuset.mems 配置相关 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
二、OOM 触发流程
2.1 完整触发流程
┌────────────────────────────────────────────────────────────────────────┐
│ OOM Killer 触发流程 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ 进程请求分配内存 │ malloc() / mmap() / page fault │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 尝试分配物理页面 │ __alloc_pages_slowpath() │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ 是 │
│ │ 分配成功? │────────────────────────▶ 返回页面 │
│ └────────┬────────┘ │
│ │ 否 │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 触发直接回收 │ __perform_reclaim() │
│ │ (Direct Reclaim)│ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ 是 │
│ │ 回收到足够内存? │────────────────────────▶ 重试分配 │
│ └────────┬────────┘ │
│ │ 否 │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 唤醒 kswapd │ wakeup_kswapd() │
│ │ 等待回收完成 │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ 是 │
│ │ 回收到足够内存? │────────────────────────▶ 重试分配 │
│ └────────┬────────┘ │
│ │ 否 │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 检查 OOM 条件 │ out_of_memory() │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ 否 │
│ │ 可以触发 OOM? │────────────────────────▶ 返回分配失败 │
│ │ (oom_killer_disabled?)│ │
│ └────────┬────────┘ │
│ │ 是 │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 选择 victim │ select_bad_process() │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 杀死进程 │ oom_kill_process() │
│ │ 发送 SIGKILL │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 等待内存释放后 │ │
│ │ 重试分配 │ │
│ └─────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
2.2 内核源码:OOM 入口
// mm/page_alloc.c
static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
struct alloc_context *ac)
{
// ... 尝试直接回收 ...
// 回收失败,检查是否需要 OOM
if (should_reclaim_retry(gfp_mask, order, ac, alloc_flags,
did_some_progress > 0, &no_progress_loops))
goto retry;
// 检查是否应该触发 OOM
if (check_retry_cpuset(cpuset_mems_cookie, ac) ||
check_retry_zonelist(zonelist))
goto retry_cpuset;
// 触发 OOM
page = __alloc_pages_may_oom(gfp_mask, order, ac, &did_some_progress);
if (page)
goto got_pg;
// ...
}
// mm/oom_kill.c
bool out_of_memory(struct oom_control *oc)
{
// 检查是否禁用了 OOM killer
if (oom_killer_disabled)
return false;
// 检查是否有进程正在退出
if (task_will_free_mem(current))
return true;
// 选择并杀死进程
select_bad_process(oc);
if (!oc->chosen) {
// 没有找到可杀的进程
dump_header(oc, NULL);
panic("Out of memory and no killable processes...\n");
}
// 杀死选中的进程
oom_kill_process(oc, "Out of memory");
return true;
}
三、oom_score 计算详解
3.1 计算公式
┌────────────────────────────────────────────────────────────────────────┐
│ oom_score 计算原理 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ 基础分数 = (进程内存占用 / 系统总内存) × 1000 │
│ │
│ 最终分数 = 基础分数 + oom_score_adj │
│ │
│ oom_score_adj 范围: -1000 ~ 1000 │
│ - -1000: 永不被 OOM Kill (OOM_SCORE_ADJ_MIN) │
│ - 0: 默认值 │
│ - 1000: 优先被 OOM Kill (OOM_SCORE_ADJ_MAX) │
│ │
│ 示例计算: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 系统内存: 8 GB │ │
│ │ 进程内存: 2 GB │ │
│ │ oom_score_adj: 0 │ │
│ │ │ │
│ │ 基础分数 = (2 GB / 8 GB) × 1000 = 250 │ │
│ │ 最终分数 = 250 + 0 = 250 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 进程 A: 内存 2GB, oom_score_adj=0 → oom_score ≈ 250 │ │
│ │ 进程 B: 内存 1GB, oom_score_adj=500 → oom_score ≈ 625 │ │
│ │ 进程 C: 内存 4GB, oom_score_adj=-500 → oom_score ≈ 0 │ │
│ │ │ │
│ │ 被杀顺序: B (625) > A (250) > C (0) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
3.2 内核源码:oom_badness 计算
// mm/oom_kill.c
/**
* oom_badness - 计算进程的 OOM 分数
* @p: 目标进程
* @totalpages: 系统总页数
*
* 返回值越高,越可能被杀死
*/
long oom_badness(struct task_struct *p, unsigned long totalpages)
{
long points;
long adj;
// 内核线程和 init 进程不能被杀
if (oom_unkillable_task(p))
return LONG_MIN;
// 正在退出的进程已经释放内存,不需要杀
if (p->mm == NULL)
return LONG_MIN;
// 获取 oom_score_adj
adj = (long)p->signal->oom_score_adj;
if (adj == OOM_SCORE_ADJ_MIN) {
// -1000 表示永不杀死
return LONG_MIN;
}
// 计算进程使用的页数
// 包括: 匿名页 + 文件页 + Swap + 页表
points = get_mm_rss(p->mm) + // RSS
get_mm_counter(p->mm, MM_SWAPENTS) + // Swap
mm_pgtables_bytes(p->mm) / PAGE_SIZE; // 页表
// 归一化到 [0, 1000] 范围
// points = (pages / totalpages) * 1000
adj *= totalpages / 1000;
points += adj;
// 确保返回值 > 0 (除非 adj = -1000)
return points > 0 ? points : 1;
}
// 选择最坏的进程
static void select_bad_process(struct oom_control *oc)
{
struct task_struct *p;
long chosen_points = LONG_MIN;
// 遍历所有进程
for_each_process(p) {
long points;
// 跳过不能杀的进程
if (oom_unkillable_task(p))
continue;
// Cgroup OOM: 只考虑 Cgroup 内的进程
if (oc->memcg && !task_in_mem_cgroup(p, oc->memcg))
continue;
// 计算分数
points = oom_badness(p, oc->totalpages);
// 选择分数最高的进程
if (points > chosen_points) {
chosen_points = points;
oc->chosen = p;
}
}
}
3.3 查看和修改 oom_score
# 查看进程的 OOM 分数
$ cat /proc/<pid>/oom_score
250
# 查看 oom_score_adj
$ cat /proc/<pid>/oom_score_adj
0
# 修改 oom_score_adj (需要 root)
# 设置为永不被 OOM kill
$ echo -1000 > /proc/<pid>/oom_score_adj
# 设置为优先被 OOM kill
$ echo 1000 > /proc/<pid>/oom_score_adj
# 批量查看所有进程的 OOM 分数
$ for pid in /proc/[0-9]*; do
name=$(cat $pid/comm 2>/dev/null)
score=$(cat $pid/oom_score 2>/dev/null)
adj=$(cat $pid/oom_score_adj 2>/dev/null)
echo "$pid $name $score $adj"
done | sort -k3 -n -r | head -20
四、Cgroup OOM 机制
4.1 Cgroup OOM vs 系统 OOM
┌────────────────────────────────────────────────────────────────────────┐
│ Cgroup OOM vs 系统 OOM │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ 系统 OOM: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Process │ │ Process │ │ Process │ │ Process │ │ │
│ │ │ A │ │ B │ │ C │ │ D │ │ │
│ │ │ 2 GB │ │ 3 GB │ │ 1 GB │ │ 2 GB │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ 系统内存: 8 GB (已用尽) │ │
│ │ OOM Killer 从所有进程中选择 (选择 B, 因为内存最大) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Cgroup OOM: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Cgroup (limit: 2GB) System (still has 4GB free) │ │
│ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │
│ │ │ ┌───────┐ ┌───────┐│ │ ┌───────┐ ┌───────┐│ │ │
│ │ │ │Proc A │ │Proc B ││ │ │Proc C │ │Proc D ││ │ │
│ │ │ │ 1.2GB │ │ 0.8GB ││ │ │ 1 GB │ │ 2 GB ││ │ │
│ │ │ └───────┘ └───────┘│ │ └───────┘ └───────┘│ │ │
│ │ │ Used: 2GB (达到限制) │ │ Not affected │ │ │
│ │ └──────────────────────┘ └──────────────────────┘ │ │
│ │ │ │
│ │ OOM Killer 只从 Cgroup 内选择 (选择 A, 因为内存最大) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
4.2 Cgroup OOM 控制
# Cgroup v1
$ cat /sys/fs/cgroup/memory/<group>/memory.oom_control
oom_kill_disable 0 # 是否禁用 OOM killer
under_oom 0 # 当前是否处于 OOM 状态
oom_kill 5 # 发生 OOM kill 的次数
# 禁用 Cgroup OOM (进程会被阻塞而非杀死)
$ echo 1 > /sys/fs/cgroup/memory/<group>/memory.oom_control
# Cgroup v2
$ cat /sys/fs/cgroup/<group>/memory.events
low 0 # 低内存警告次数
high 10 # 达到 high 水位次数
max 3 # 达到 max 限制次数
oom 2 # OOM 事件次数
oom_kill 2 # OOM kill 次数
oom_group_kill 0 # 整组 OOM kill 次数
# 查看当前内存使用
$ cat /sys/fs/cgroup/<group>/memory.current
1073741824
# 查看内存限制
$ cat /sys/fs/cgroup/<group>/memory.max
2147483648
4.3 内核源码:Cgroup OOM
// mm/memcontrol.c
// Cgroup 内存分配检查
static int try_charge(struct mem_cgroup *memcg, gfp_t gfp_mask,
unsigned int nr_pages)
{
unsigned long batch = max(MEMCG_CHARGE_BATCH, nr_pages);
int ret;
// 尝试 charge
ret = try_charge_memcg(memcg, gfp_mask, batch);
if (ret == 0)
return 0;
// charge 失败,尝试回收
mem_cgroup_reclaim(memcg, gfp_mask);
// 再次尝试
ret = try_charge_memcg(memcg, gfp_mask, batch);
if (ret == 0)
return 0;
// 回收失败,触发 OOM
mem_cgroup_oom(memcg, gfp_mask, order);
return 0;
}
// Cgroup OOM 处理
static void mem_cgroup_oom(struct mem_cgroup *memcg,
gfp_t mask, int order)
{
struct oom_control oc = {
.zonelist = NULL,
.nodemask = NULL,
.memcg = memcg, // 指定 Cgroup
.gfp_mask = mask,
.order = order,
};
// 检查是否禁用了 OOM killer
if (memcg->oom_kill_disable) {
// 阻塞进程而不是杀死
current->memcg_in_oom = memcg;
wait_event(memcg->oom_waitq, !READ_ONCE(memcg->under_oom));
return;
}
// 触发 Cgroup OOM
out_of_memory(&oc);
}
五、OOM 防护策略
5.1 系统级防护
# 1. 禁用 overcommit
# 0 = 允许适度 overcommit (默认)
# 1 = 始终允许 overcommit
# 2 = 禁止 overcommit (严格模式)
$ echo 2 > /proc/sys/vm/overcommit_memory
# 设置 overcommit 比例 (仅 overcommit_memory=2 时有效)
# 允许分配: (物理内存 + Swap) * overcommit_ratio%
$ echo 80 > /proc/sys/vm/overcommit_ratio
# 2. 增加 Swap
$ fallocate -l 4G /swapfile
$ chmod 600 /swapfile
$ mkswap /swapfile
$ swapon /swapfile
# 3. 调整 swappiness
# 0 = 尽量不使用 swap
# 100 = 积极使用 swap
$ echo 10 > /proc/sys/vm/swappiness
# 4. 设置 OOM panic
# 发生 OOM 时直接 panic 而非杀进程
$ echo 1 > /proc/sys/vm/panic_on_oom
5.2 进程级防护
# 1. 设置关键进程不被 OOM kill
$ echo -1000 > /proc/<pid>/oom_score_adj
# 2. 使用 systemd 配置
# /etc/systemd/system/critical-service.service
[Service]
OOMScoreAdjust=-1000
# 3. 使用 Docker 配置
$ docker run --oom-score-adj=-500 myimage
# 4. Kubernetes Pod 配置
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
resources:
limits:
memory: "1Gi"
requests:
memory: "512Mi"
5.3 容器级防护
# Kubernetes Pod 资源限制最佳实践
apiVersion: v1
kind: Pod
metadata:
name: memory-safe-pod
spec:
containers:
- name: app
image: myapp
resources:
# 设置合理的 requests 和 limits
requests:
memory: "256Mi" # 保证能获得的内存
cpu: "100m"
limits:
memory: "512Mi" # 最大内存限制
cpu: "500m"
# 健康检查
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
# 优雅终止
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
---
# 使用 PodDisruptionBudget 防止批量 OOM
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: myapp
六、OOM 问题排查
6.1 识别 OOM 事件
# 1. 查看内核日志
$ dmesg | grep -i "oom\|killed"
[12345.678901] Out of memory: Killed process 1234 (myapp)
# 2. 查看 journalctl
$ journalctl -k | grep -i "oom\|killed"
# 3. 查看 messages 日志
$ grep -i "oom" /var/log/messages
# 4. 典型的 OOM 日志示例
[12345.678901] myapp invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
[12345.678902] CPU: 0 PID: 1234 Comm: myapp Not tainted 5.4.0 #1
[12345.678903] Memory cgroup out of memory: Killed process 1234 (myapp) total-vm:2097152kB, anon-rss:1048576kB, file-rss:4096kB, shmem-rss:0kB
[12345.678904] oom_reaper: reaped process 1234 (myapp), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
6.2 分析 OOM 日志
┌────────────────────────────────────────────────────────────────────────┐
│ OOM 日志解读 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Memory cgroup out of memory: Killed process 1234 (myapp) │
│ ↑ ↑ ↑ │
│ │ │ └─ 进程名 │
│ │ └─ 进程 PID │
│ └─ Cgroup OOM (不是系统 OOM) │
│ │
│ total-vm:2097152kB ← 虚拟内存总量 (约 2 GB) │
│ anon-rss:1048576kB ← 匿名内存 RSS (约 1 GB) │
│ file-rss:4096kB ← 文件映射 RSS (约 4 MB) │
│ shmem-rss:0kB ← 共享内存 RSS │
│ │
│ oom_score_adj=0 ← OOM 调整值 (默认) │
│ │
│ 关键信息: │
│ - 进程使用了约 1 GB 匿名内存 │
│ - 这是 Cgroup OOM,不是系统内存不足 │
│ - 检查容器/Cgroup 的内存限制设置 │
│ │
└────────────────────────────────────────────────────────────────────────┘
6.3 排查脚本
#!/bin/bash
# oom_analysis.sh - OOM 问题分析脚本
echo "=== 系统内存状态 ==="
free -h
echo
echo "=== 内存详细信息 ==="
cat /proc/meminfo | grep -E "^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree)"
echo
echo "=== Top 10 内存使用进程 ==="
ps aux --sort=-%mem | head -11
echo
echo "=== OOM 相关日志 ==="
dmesg | grep -i "oom\|killed" | tail -20
echo
echo "=== Cgroup 内存使用 (Docker) ==="
for dir in /sys/fs/cgroup/memory/docker/*/; do
if [ -d "$dir" ]; then
container_id=$(basename "$dir")
usage=$(cat "$dir/memory.usage_in_bytes" 2>/dev/null)
limit=$(cat "$dir/memory.limit_in_bytes" 2>/dev/null)
oom_kill=$(cat "$dir/memory.oom_control" 2>/dev/null | grep oom_kill | awk '{print $2}')
if [ -n "$usage" ] && [ -n "$limit" ]; then
usage_mb=$((usage / 1024 / 1024))
limit_mb=$((limit / 1024 / 1024))
percent=$((usage * 100 / limit))
echo "Container: ${container_id:0:12} | Usage: ${usage_mb}MB / ${limit_mb}MB (${percent}%) | OOM kills: $oom_kill"
fi
fi
done
echo
echo "=== 高 OOM Score 进程 ==="
for pid in /proc/[0-9]*; do
if [ -d "$pid" ]; then
pid_num=$(basename "$pid")
name=$(cat "$pid/comm" 2>/dev/null)
score=$(cat "$pid/oom_score" 2>/dev/null)
adj=$(cat "$pid/oom_score_adj" 2>/dev/null)
if [ -n "$score" ] && [ "$score" -gt 100 ]; then
echo "PID: $pid_num | Name: $name | Score: $score | Adj: $adj"
fi
fi
done | sort -t: -k4 -n -r | head -10
6.4 预防性监控
#!/usr/bin/env python3
# memory_monitor.py - 内存监控与预警
import os
import time
import subprocess
def get_memory_info():
"""获取系统内存信息"""
with open('/proc/meminfo') as f:
meminfo = {}
for line in f:
parts = line.split(':')
key = parts[0].strip()
value = int(parts[1].strip().split()[0])
meminfo[key] = value
return meminfo
def get_cgroup_memory():
"""获取 Cgroup 内存使用"""
cgroups = []
cgroup_base = '/sys/fs/cgroup/memory'
if os.path.exists(cgroup_base + '/docker'):
for container in os.listdir(cgroup_base + '/docker'):
path = f"{cgroup_base}/docker/{container}"
if os.path.isdir(path):
try:
with open(f"{path}/memory.usage_in_bytes") as f:
usage = int(f.read().strip())
with open(f"{path}/memory.limit_in_bytes") as f:
limit = int(f.read().strip())
cgroups.append({
'id': container[:12],
'usage': usage,
'limit': limit,
'percent': usage * 100 / limit
})
except:
pass
return cgroups
def check_and_alert():
"""检查并告警"""
meminfo = get_memory_info()
total = meminfo['MemTotal']
available = meminfo['MemAvailable']
percent_used = (total - available) * 100 / total
# 系统级告警
if percent_used > 90:
print(f"[CRITICAL] System memory usage: {percent_used:.1f}%")
elif percent_used > 80:
print(f"[WARNING] System memory usage: {percent_used:.1f}%")
# Cgroup 级告警
for cg in get_cgroup_memory():
if cg['percent'] > 90:
print(f"[CRITICAL] Container {cg['id']} memory: {cg['percent']:.1f}%")
elif cg['percent'] > 80:
print(f"[WARNING] Container {cg['id']} memory: {cg['percent']:.1f}%")
if __name__ == "__main__":
while True:
check_and_alert()
time.sleep(10)
七、实战练习
7.1 模拟 OOM
#!/bin/bash
# trigger_oom.sh - 触发 OOM 并观察
# 创建测试 Cgroup (Cgroup v2)
CGROUP_PATH="/sys/fs/cgroup/test-oom"
mkdir -p $CGROUP_PATH
# 设置内存限制 100MB
echo "100M" > $CGROUP_PATH/memory.max
# 将当前 shell 加入 Cgroup
echo $$ > $CGROUP_PATH/cgroup.procs
echo "=== 开始消耗内存 ==="
echo "内存限制: $(cat $CGROUP_PATH/memory.max)"
echo "当前使用: $(cat $CGROUP_PATH/memory.current)"
# 启动内存消耗程序
python3 -c "
import time
data = []
try:
while True:
# 每次分配 10MB
data.append('x' * (10 * 1024 * 1024))
print(f'Allocated: {len(data) * 10} MB')
time.sleep(0.5)
except MemoryError:
print('MemoryError!')
"
# 检查 OOM 事件
echo "=== OOM 事件 ==="
cat $CGROUP_PATH/memory.events
# 清理
rmdir $CGROUP_PATH
7.2 保护关键进程
#!/bin/bash
# protect_process.sh - 保护关键进程不被 OOM Kill
CRITICAL_SERVICES=("sshd" "systemd" "kubelet")
for service in "${CRITICAL_SERVICES[@]}"; do
pids=$(pgrep -x "$service")
for pid in $pids; do
if [ -f "/proc/$pid/oom_score_adj" ]; then
echo -1000 > /proc/$pid/oom_score_adj
echo "Protected $service (PID: $pid) from OOM"
fi
done
done
# 验证
echo
echo "=== 受保护的进程 ==="
for pid in /proc/[0-9]*; do
adj=$(cat "$pid/oom_score_adj" 2>/dev/null)
if [ "$adj" = "-1000" ]; then
name=$(cat "$pid/comm" 2>/dev/null)
echo "$(basename $pid) $name"
fi
done
八、面试要点
8.1 高频问题
OOM Killer 是什么?什么时候触发?
- 内存保护机制,防止系统崩溃
- 当内存耗尽且无法回收时触发
oom_score 是怎么计算的?
- 基于进程内存占用比例
- 加上 oom_score_adj 调整
- 分数越高越先被杀
如何防止进程被 OOM Kill?
- 设置 oom_score_adj = -1000
- 合理设置资源限制
- 增加系统内存或 Swap
Cgroup OOM 和系统 OOM 的区别?
- Cgroup OOM: 容器内存达限制
- 系统 OOM: 整个系统内存耗尽
- Cgroup OOM 只影响容器内进程
8.2 进阶问题
OOM Killer 选择 victim 的算法?
- 遍历所有进程计算 oom_badness
- 选择分数最高的进程
- 考虑子进程和线程组
如何调试 OOM 问题?
- 查看 dmesg/journalctl 日志
- 分析内存使用趋势
- 检查 Cgroup 限制设置
overcommit 对 OOM 的影响?
- overcommit_memory=2 可防止 OOM
- 但可能导致分配失败
- 权衡可用性和稳定性
相关链接
- 19-Linux内存管理深度剖析 - 内存管理基础
- 21-Cgroup内存控制深度 - 容器内存限制
- 22-Kubernetes资源模型 - K8s 资源管理
下一步:让我们深入了解 Cgroup 是如何限制容器内存的!