Linux 内存优化:从 OOM 到稳定运行的内核调优实践 Linux 内存优化从 OOM 到稳定运行的内核调优实践一、当进程被 OOM KillLinux 内存管理的运维困境Linux 的 OOM Killer 是内核在内存不足时的最后防线——选择一个进程终止以释放内存。但 OOM Killer 的选择策略基于进程的 oom_score与内存占用和优先级相关高内存占用的进程如数据库、Java 应用往往首当其冲。一次 OOM Kill 可能导致核心服务中断而更隐蔽的问题是频繁的内存回收Direct Reclaim导致应用响应延迟飙升但进程并未被 Kill。Linux 内存优化的核心目标是在物理内存有限的前提下最大化有效内存使用减少内存回收对应用性能的影响。这需要深入理解 Linux 内存管理的底层机制。二、Linux 内存管理模型从虚拟地址到物理页帧flowchart TD A[进程虚拟地址空间] -- B[页表映射] B -- C[物理页帧] C -- D{页帧状态} D -- E[活跃页: 进程正在使用] D -- F[非活跃页: 最近未访问] D -- G[可回收页: Cache/Buffer] subgraph 内存回收路径 H[kswapd 后台回收] -- F I[Direct Reclaim 同步回收] -- G end subgraph OOM 流程 J[内存不足] -- K{可回收?} K --|是| L[回收 Cache] K --|否| M[OOM Killer] M -- N[选择 oom_score 最高进程] N -- O[终止进程] end style I fill:#ff6b6b,color:#fff style M fill:#ff6b6b,color:#fff style H fill:#51cf66,color:#fff三、生产级 Linux 内存优化方案3.1 内核参数调优#!/bin/bash # Linux 内存优化内核参数配置 # Swappiness 调优 # 控制内核将匿名页换出到 Swap 的倾向 # 默认值 60对数据库和 Java 应用建议设为 1-10 # 值越低内核越倾向于回收 Cache 而非换出匿名页 sysctl -w vm.swappiness1 # 脏页回写调优 # 脏页占比达到 dirty_ratio 时阻塞写入进程同步回写 sysctl -w vm.dirty_ratio10 # 脏页占比达到 dirty_background_ratio 时后台异步回写 sysctl -w vm.dirty_background_ratio5 # 脏页最大存活时间厘秒 sysctl -w vm.dirty_expire_centisecs3000 # 内存过量分配策略 # 0: 不允许过量分配严格模式 # 1: 允许过量分配内存不足时 OOM Kill # 2: 允许过量分配但限制为 Swap 物理内存 * overcommit_ratio sysctl -w vm.overcommit_memory1 # overcommit_memory2 时的物理内存使用比例百分比 sysctl -w vm.overcommit_ratio80 # 最小空闲内存 # 内核保留的最小空闲内存页数 # 当空闲内存低于此值时触发 kswapd 回收 # 建议设为总内存的 1%-3% TOTAL_MEM_KB$(grep MemTotal /proc/meminfo | awk {print $2}) MIN_FREE$((TOTAL_MEM_KB / 100 * 1)) # 1% of total memory sysctl -w vm.min_free_kbytes$MIN_FREE # OOM Killer 调优 # 禁止内核完全禁用 OOM Killer设为 1 可禁用但不推荐 sysctl -w vm.oom_kill_allocating_task0 # 透明大页 # 对数据库应用建议关闭减少内存碎片和延迟抖动 echo never /sys/kernel/mm/transparent_hugepage/enabled echo never /sys/kernel/mm/transparent_hugepage/defrag # 持久化配置 cat /etc/sysctl.d/99-memory-tuning.conf EOF vm.swappiness1 vm.dirty_ratio10 vm.dirty_background_ratio5 vm.dirty_expire_centisecs3000 vm.overcommit_memory1 vm.min_free_kbytes$MIN_FREE EOF3.2 进程级内存控制# Cgroup v2 内存限制 # 为服务设置内存限制防止单个服务耗尽系统内存 # 创建 cgroup mkdir -p /sys/fs/cgroup/api-server # 设置内存限制2GB echo 2147483648 /sys/fs/cgroup/api-server/memory.max # 设置内存软限制1.5GB超过时开始回收但不 Kill echo 1610612736 /sys/fs/cgroup/api-server/memory.high # 禁止 OOM Kill内存超限后进程被暂停而非 Kill echo 1 /sys/fs/cgroup/api-server/memory.oom.group # 将进程加入 cgroup echo $PID /sys/fs/cgroup/api-server/cgroup.procs # 查看内存使用情况 cat /sys/fs/cgroup/api-server/memory.current cat /sys/fs/cgroup/api-server/memory.events# OOM Score 调优 # 保护关键进程不被 OOM Kill # 查看进程的 oom_score cat /proc/$PID/oom_score # 设置 oom_score_adj-1000 到 1000 # -1000 表示永远不会被 OOM Kill echo -500 /proc/$PID/oom_score_adj # 保护数据库进程 DB_PID$(pgrep -f postgres.*main) echo -1000 /proc/$DB_PID/oom_score_adj # 保护 SSH 守护进程 SSHD_PID$(pgrep -f /usr/sbin/sshd) echo -1000 /proc/$SSHD_PID/oom_score_adj3.3 内存监控脚本#!/bin/bash # Linux 内存监控脚本检测内存压力和 OOM 风险 # 内存使用率 MEM_TOTAL$(free -m | awk /Mem:/ {print $2}) MEM_USED$(free -m | awk /Mem:/ {print $3}) MEM_AVAILABLE$(free -m | awk /Mem:/ {print $7}) MEM_USAGE_PCT$((MEM_USED * 100 / MEM_TOTAL)) # Swap 使用率 SWAP_TOTAL$(free -m | awk /Swap:/ {print $2}) SWAP_USED$(free -m | awk /Swap:/ {print $3}) SWAP_USAGE_PCT0 if [ $SWAP_TOTAL -gt 0 ]; then SWAP_USAGE_PCT$((SWAP_USED * 100 / SWAP_TOTAL)) fi # 内存压力评估 if [ $MEM_USAGE_PCT -gt 95 ]; then echo CRITICAL: 内存使用率 ${MEM_USAGE_PCT}%可用 ${MEM_AVAILABLE}MB # 列出内存占用 Top 5 进程 echo 内存占用 Top 5: ps aux --sort-%mem | head -6 elif [ $MEM_USAGE_PCT -gt 85 ]; then echo WARNING: 内存使用率 ${MEM_USAGE_PCT}%可用 ${MEM_AVAILABLE}MB fi # Swap 使用评估 if [ $SWAP_USAGE_PCT -gt 50 ]; then echo WARNING: Swap 使用率 ${SWAP_USAGE_PCT}%可能存在内存压力 fi # 检查 OOM 事件 OOM_COUNT$(dmesg | grep -c Out of memory 2/dev/null || echo 0) if [ $OOM_COUNT -gt 0 ]; then echo WARNING: 检测到 ${OOM_COUNT} 次 OOM 事件 dmesg | grep Out of memory | tail -5 fi # 检查 Direct Reclaim 事件 PGSCAN_DIRECT$(cat /proc/vmstat | grep pgscan_direct | awk {sum$2} END {print sum}) PGSCAN_KSWAPD$(cat /proc/vmstat | grep pgscan_kswapd | awk {sum$2} END {print sum}) if [ $PGSCAN_DIRECT -gt 0 ]; then DIRECT_RATIO$((PGSCAN_DIRECT * 100 / (PGSCAN_DIRECT PGSCAN_KSWAPD 1))) if [ $DIRECT_RATIO -gt 20 ]; then echo WARNING: Direct Reclaim 占比 ${DIRECT_RATIO}%应用可能受延迟影响 fi fi3.4 内存泄漏检测#!/bin/bash # 内存泄漏检测持续监控进程内存增长趋势 PID$1 INTERVAL60 # 采样间隔秒 SAMPLES30 # 采样次数 if [ -z $PID ]; then echo Usage: $0 PID exit 1 fi echo 监控进程 $PID 的内存使用趋势${SAMPLES}次采样间隔${INTERVAL}秒 echo 时间, RSS(MB), VSZ(MB), 增长(MB) PREV_RSS0 for i in $(seq 1 $SAMPLES); do if [ ! -d /proc/$PID ]; then echo 进程 $PID 已退出 break fi RSS$(cat /proc/$PID/status | grep VmRSS | awk {print $2}) VSZ$(cat /proc/$PID/status | grep VmSize | awk {print $2}) RSS_MB$((RSS / 1024)) VSZ_MB$((VSZ / 1024)) GROWTH0 if [ $PREV_RSS -gt 0 ]; then GROWTH$((RSS_MB - PREV_RSS)) fi echo $(date %H:%M:%S), ${RSS_MB}, ${VSZ_MB}, ${GROWTH} PREV_RSS$RSS_MB sleep $INTERVAL done # 分析增长趋势 echo echo 分析结果 FIRST_RSS$(head -2 $OUTPUT | tail -1 | awk -F, {print $2}) LAST_RSS$(tail -1 $OUTPUT | awk -F, {print $2}) if [ -n $FIRST_RSS ] [ -n $LAST_RSS ]; then TOTAL_GROWTH$((LAST_RSS - FIRST_RSS)) if [ $TOTAL_GROWTH -gt 100 ]; then echo 警告: 内存增长 ${TOTAL_GROWTH}MB可能存在内存泄漏 else echo 内存增长 ${TOTAL_GROWTH}MB在正常范围内 fi fi四、Linux 内存优化的代价与架构权衡Linux 内存优化方案的代价需要审慎评估Swappiness 极低的风险将 swappiness 设为 1 可以减少 Swap 使用但在内存真正不足时内核会直接触发 OOM Kill 而非换出页面。对于可以容忍延迟但不能容忍进程被 Kill 的场景如数据库适度的 Swap 可以作为缓冲区。Cgroup 内存限制的副作用设置 memory.high 后进程超过软限制时会被限流throttled导致延迟增加。设置 memory.max 后进程超过硬限制会被 OOM Kill。需要根据进程的内存使用模式设置合理的限制值但内存使用模式可能随负载变化。关闭透明大页的影响关闭 THP 可以减少内存碎片和延迟抖动但会增加 TLB Miss 的次数对内存密集型应用如科学计算可能降低性能。需要根据应用特征决定是否关闭。适用边界内核参数调优适合所有 Linux 服务器Cgroup 内存限制适合容器化部署OOM Score 调优适合保护关键进程。禁用场景当应用使用大量内存映射文件如数据库的 shared_buffers时过低的 swappiness 可能导致 Cache 被过度回收反而降低性能。五、总结Linux 内存优化的核心是减少内存回收对应用的影响。Swappiness 调优控制了 Swap 使用的倾向脏页回写参数减少了 I/O 突发Cgroup 内存限制防止单进程耗尽系统内存OOM Score 保护关键进程不被误杀。在实际落地中建议先监控内存使用模式可用内存、Swap 使用、Direct Reclaim 比例再针对性调优。核心原则是内存优化的目标不是最大化内存利用率而是最小化内存压力对应用性能的影响。