Linux服务器在运行高内存负载服务时,系统级OOM Killer和cgroup内存限制是两个核心的内存管理机制。理解OOM Killer的进程选择算法和cgroup v2的内存控制接口,对于保障关键服务稳定性至关重要。本文从内核机制分析到实际配置操作,梳理服务器内存管理的完整实践路径。
OOM Killer进程选择算法分析
当系统可用内存低于阈值时,内核触发OOM Killer,选择一个最该被杀死的进程释放内存。选择依据是oom_score,每个进程的oom_score由多个因素计算得出。
查看进程的oom_score:
# 查看指定进程的oom_score(越大越容易被kill)
cat /proc/<pid>/oom_score
# 查看oom_score_adj(可手动调整,范围-1000到1000)
cat /proc/<pid>/oom_score_adj
# 按oom_score排序,找出最容易被kill的进程
for pid in $(ls /proc | grep -E '^[0-9]+$'); do
score=$(cat /proc/$pid/oom_score 2>/dev/null)
name=$(cat /proc/$pid/comm 2>/dev/null)
echo "$score $pid $name"
done | sort -rn | head -20
oom_score的计算因素包括:
- 进程内存占用(RSS + page cache)
- 子进程内存(子进程的内存计入父进程评分)
- 运行时间(运行越久评分越低,降低被kill概率)
- 进程权限(root进程评分降低)
- oom_score_adj手动调整值
保护关键服务不被OOM Killer杀死,设置oom_score_adj为-1000:
# 方法一:通过procfs临时设置
echo -1000 > /proc/<pid>/oom_score_adj
# 方法二:通过systemd永久设置
[Service]
OOMScoreAdjust=-1000
# 方法三:对非systemd管理的进程,用choom工具
choom -p <pid> -s -1000
cgroup v2内存限制配置
cgroup v2统一了资源控制器,内存控制接口位于/sys/fs/cgroup/路径下。与cgroup v1相比,v2的内存统计更精确,支持内存压力通知机制。
使用systemd创建受限服务:
# 创建slice限制内存
cat > /etc/systemd/system/limited-app.slice << 'EOF'
[Slice]
MemoryMax=2G
MemoryHigh=1500M
MemorySwapMax=2G
MemoryZSwapWriteback=on
EOF
systemctl daemon-reload
# 在service文件中指定slice
[Service]
Slice=limited-app.slice
ExecStart=/usr/bin/python3 /opt/app/server.py
关键内存控制参数说明:
- MemoryMax:硬限制,超过后触发cgroup级OOM,杀死该cgroup内内存占用最大的进程
- MemoryHigh:软限制,超过后内核通过回收页面降低内存占用,不立即杀进程
- MemorySwapMax:交换空间限制,设为0可禁用swap
- MemoryZSwapWriteback:启用zswap回写,压缩冷页面
直接通过cgroupfs操作(非systemd环境):
# 创建cgroup
mkdir /sys/fs/cgroup/app_group
# 设置内存限制为2GB
echo "2G" > /sys/fs/cgroup/app_group/memory.max
# 设置软限制
echo "1500M" > /sys/fs/cgroup/app_group/memory.high
# 禁用swap
echo "0" > /sys/fs/cgroup/app_group/memory.swap.max
# 将进程加入cgroup
echo <pid> > /sys/fs/cgroup/app_group/cgroup.procs
# 查看当前内存使用
cat /sys/fs/cgroup/app_group/memory.current
# 输出示例:1683045376
# 查看内存事件计数
cat /sys/fs/cgroup/app_group/memory.events
# 输出示例:
# oom 3
# oom_kill 3
# oom_group_kill 3
# high 127
内存压力通知与主动防御
cgroup v2提供PSI(Pressure Stall Information)接口,实时反映内存压力状态。结合PSI可以在OOM发生前采取预防措施。
# 查看cgroup的PSI指标
cat /sys/fs/cgroup/app_group/memory.pressure
# 输出:
# some avg10=12.34 avg60=8.91 avg300=5.67 total=892345678
# full avg10=5.23 avg60=3.45 avg300=1.89 total=345678901
# some:至少一个任务因内存压力停滞
# full:所有任务都因内存压力停滞
# avg10/avg60/avg300:10秒/60秒/300秒平均停滞时间百分比
通过PSI事件触发自动扩容或限流:
import os
import select
def setup_pressure_notification(cgroup_path):
"""Register PSI event notification"""
psi_fd = os.open(f"{cgroup_path}/memory.pressure", os.O_RDWR)
# Write listen condition
os.write(psi_fd, b"some 150000 1000") # 150ms window, 1ms threshold
# Use epoll to listen
epoll = select.epoll()
epoll.register(psi_fd, select.EPOLLIN)
while True:
events = epoll.poll()
for fd, event in events:
data = os.read(fd, 256)
handle_pressure_event(data)
内存泄漏排查与进程级监控
cgroup的memory.stat提供详细的内存分项统计,帮助定位内存泄漏来源:
# 查看cgroup内存统计详情
cat /sys/fs/cgroup/app_group/memory.stat | head -30
# 关键指标:
# anon 1073741824 匿名页(堆栈等)
# file 536870912 文件页(mmap等)
# slab 67108864 内核slab缓存
# pgfault 12345678 页面错误次数
# pgmajfault 1234 主要页面错误次数
# inactive_anon 268435456 非活跃匿名页
# active_anon 805306368 活跃匿名页
持续增长的anon值通常指向内存泄漏。配合smem工具按进程查看实际内存占用:
# 安装smem
yum install -y smem || apt install -y smem
# 按USS(独占内存)排序
smem -rs uss | head -20
# 查看某个cgroup下所有进程的内存
smem --cgrouppath=/sys/fs/cgroup/app_group -rs pss
对于Java应用,JVM堆内存与cgroup限制的配合需要特别注意。JVM默认按系统总内存计算堆大小,在容器环境中会导致超过cgroup限制:
# 使用JVM容器感知参数(JDK 10+自动启用)
java -XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:InitialRAMPercentage=50.0 \
-XX:MinRAMPercentage=25.0 \
-jar app.jar
# JDK 8环境手动设置
java -XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap \
-XX:MaxRAMFraction=2 \
-jar app.jar
MaxRAMPercentage设为75%保留25%给非堆内存(metaspace、线程栈、direct buffer等),避免cgroup OOM。配合-XX:MaxDirectMemorySize限制NIO直接内存,防止Netty等框架的direct buffer泄漏导致整体内存超限。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/linux-fu-wu-qi-nei-cun-guan-li-shi-zhan-oomkiller-ji-zhi/