12. OOM Killer机制全解析OOM KillerOut-of-Memory Killer是Linux内核的最后一道安全阀当所有内存回收方法kswapd异步回收、直接回收、OOM前的最后回收都失败时内核会杀死一个或多个进程释放物理内存避免系统崩溃。很多线上服务的“意外退出”、“核心业务中断”根源都是OOM误杀比如杀死了核心业务进程而不是内存泄漏的后台进程。本章节基于Linux 6.6 LTS内核完整拆解OOM Killer的触发条件、进程选择算法、核心调优参数、工程实践与避坑指南。12.1 OOM Killer的触发条件OOM Killer的触发条件非常严格只有当所有内存回收方法都失败且没有足够的空闲内存分配时才会触发完整触发流程进程调用内存分配接口比如alloc_pages、kmalloc↓1. 快速路径分配失败↓2. 慢速路径分配├→ 唤醒kswapd异步回收├→ 直接回收内存├→ OOM前的最后回收│ ├→ 回收所有可回收的slab对象│ ├→ 强制回收共享库的代码段│ ├→ 触发内存碎片整理│ └→ 尝试杀死低优先级的内核线程└→ 所有回收方法都失败↓3. 检查是否有足够的空闲内存如果没有触发OOM Killer↓4. OOM Killer选择一个或多个进程杀死它们↓5. 释放被杀死进程的内存↓6. 重新尝试内存分配核心工程细节OOM前的最后回收内核会先尝试所有可能的回收方法只有当所有方法都失败时才会触发OOM Killer不会轻易杀死进程OOM Killer的触发是全局的默认情况下OOM Killer会扫描系统中的所有进程选择一个score最高的进程杀死如果使用memcgOOM Killer会只扫描该memcg中的进程选择一个score最高的进程杀死被杀死进程的内存会被立即释放包括物理页、页表、VMA、文件描述符等释放的内存会被立即分配给触发OOM的进程。12.2 OOM Killer的进程选择算法OOM Killer的进程选择算法是“选择对系统影响最小释放内存最多的进程”**核心分为三个步骤计算每个进程的原始OOM分数badness根据进程的oom_score_adj调整分数选择分数最高的进程杀死。12.2.1 原始OOM分数的计算原始OOM分数的计算公式简化版badness (total_vm swap_vm) * 10 / totalram_pages oom_score_adj其中total_vm进程的总虚拟内存大小单位页swap_vm进程的swap占用大小单位页totalram_pages系统的总物理内存大小单位页oom_score_adj进程的OOM调整分数范围-1000~1000。原始OOM分数的核心影响因素进程的内存占用内存占用越大分数越高越容易被杀死进程的运行时间运行时间越短分数越高越容易被杀死因为对系统的影响小进程的nice值nice值越高优先级越低分数越高越容易被杀死进程是否是root进程root进程的分数会降低3%不容易被杀死进程是否是init进程PID 1init进程的分数会被设置为0永远不会被杀死。12.2.2 oom_score_adj的调整oom_score_adj是用户态可以控制的OOM调整分数范围-1000~1000是避免OOM误杀的核心手段-1000禁止被OOM杀死进程的分数会被设置为00不调整分数使用原始分数1000优先被OOM杀死进程的分数会被设置为1000。12.3 OOM Killer的核心调优参数OOM Killer的行为由/proc/sys/vm/下的参数控制核心参数参数名默认值核心含义调优场景oom_kill_allocating_task0是否杀死触发OOM的进程1杀死0不杀死选择score最高的进程测试环境或者触发OOM的进程是内存泄漏的进程设置为1直接杀死触发OOM的进程生产环境设置为0选择score最高的进程杀死panic_on_oom0OOM时是否panic系统崩溃0不panic1panic2panic如果是memcg OOM高可用集群设置为1OOM时系统崩溃由集群管理软件比如Kubernetes重启节点避免核心业务进程被误杀后继续运行生产环境设置为0OOM时杀死进程释放内存系统继续运行oom_dump_tasks1OOM时是否打印所有进程的内存信息1打印0不打印生产环境设置为1OOM时打印所有进程的内存信息方便排查OOM的原因测试环境设置为0减少日志输出sysctl_vm_oom_adj已废弃旧的OOM调整参数范围-17~15已被oom_score_adj替代不要使用使用oom_score_adj替代12.4 工程实践与避坑指南1.OOM的排查流程当系统发生OOM时按照以下流程排查a查看内核日志用dmesg或journalctl -k查看OOM的详细记录包括触发OOM的进程被杀死的进程被杀死进程的内存信息total-vm、anon-rss、file-rss、shmem-rss系统的内存信息MemTotal、MemFree、MemAvailable、Buffers、Cached、SwapTotal、SwapFreeb.分析被杀死进程的内存占用查看被杀死进程的anon-rss匿名页物理内存占用如果anon-rss很大说明进程可能有内存泄漏c.分析触发OOM的进程查看触发OOM的进程的内存占用如果触发OOM的进程是内存泄漏的进程设置oom_kill_allocating_task1直接杀死触发OOM的进程d.检查系统的内存配置查看min_free_kbytes、swappiness、watermark_scale_factor的配置确认是否合理e.检查memcg的配置如果使用memcg查看memcg的内存限制确认是否设置过低f.修复内存泄漏如果进程有内存泄漏用Valgrind、AddressSanitizer、perf mem等工具定位内存泄漏的位置修复泄漏。2.避免OOM误杀的最佳实践a.调整核心业务进程的oom_score_adj设置为-1000禁止被OOM杀死# 临时调整重启后失效 echo -1000 /proc/12345/oom_score_adj # 永久调整systemd服务 [Service] OOMScoreAdjust-1000b.调整内存泄漏的后台进程的oom_score_adj设置为1000优先被OOM杀死c.使用memcg限制进程的内存占用为每个业务进程创建独立的memcg设置内存限制避免单个进程占用过多的内存触发全局OOMd.调大min_free_kbytes和watermark_scale_factor提前触发kswapd异步回收避免直接回收和OOMe.调小swappiness数据库等延迟敏感的服务设置为0禁止回收匿名页避免swap缺页异常f.监控系统的内存使用情况用PrometheusGrafana监控系统的MemAvailable、SwapFree、kswapd的CPU使用率、pgscan_direct/pgsteal_direct提前发现内存不足的问题扩容物理内存或优化程序的内存占用。3.memcg OOM的配置与排查memcg内存控制组是Linux内核的资源隔离机制可以限制进程组的内存占用当进程组的内存占用超过限制时会触发memcg OOM只杀死该进程组中的进程不会影响其他进程组。memcg OOM的配置# 1. 创建一个memcg名为my-service mkdir /sys/fs/cgroup/memory/my-service # 2. 设置memcg的内存限制为8GB echo 8589934592 /sys/fs/cgroup/memory/my-service/memory.limit_in_bytes # 3. 设置memcg的swap限制为0禁止使用swap echo 0 /sys/fs/cgroup/memory/my-service/memory.memsw.limit_in_bytes # 4. 把进程加入到memcg中 echo 12345 /sys/fs/cgroup/memory/my-service/cgroup.procs # 5. 永久调整systemd服务 [Service] MemoryMax8G MemorySwapMax0memcg OOM的排查# 查看memcg的内存使用情况 cat /sys/fs/cgroup/memory/my-service/memory.usage_in_bytes # 查看memcg的内存限制 cat /sys/fs/cgroup/memory/my-service/memory.limit_in_bytes # 查看memcg的OOM记录 cat /sys/fs/cgroup/memory/my-service/memory.oom_control # 查看memcg的OOM统计 cat /sys/fs/cgroup/memory/my-service/memory.events避坑指南不要把所有进程的oom_score_adj都设置为-1000如果所有进程都禁止被OOM杀死OOM时系统会panic导致系统崩溃不要把panic_on_oom设置为1除非是高可用集群如果不是高可用集群OOM时系统崩溃会导致所有业务中断损失更大不要忽略memcg OOMmemcg OOM的日志不会打印到dmesg中只会打印到memcg的memory.oom_control和memory.events中需要单独监控不要在生产环境频繁调整oom_score_adj频繁调整oom_score_adj会导致OOM Killer的进程选择算法出现混乱甚至触发OOM误杀。