第七篇:《内存调优与 OOM 问题排查》
即使内存管理机制再完善当物理内存被耗尽时Linux 内核必须做出抉择杀掉哪个进程来释放内存 这个“挥刀”的决定由 OOM Killer 执行。本文深入讲解 OOM Killer 的评分机制、如何保护关键进程不被误杀、如何分析 OOM 日志定位根因以及 Cgroups v2 下的内存限制策略。同时介绍内存碎片问题与回收机制帮你建立完整的内存故障排查能力。一、OOM Killer内存耗尽时的“最后裁决”当系统物理内存包括 Swap耗尽时内核会触发 OOM KillerOut-Of-Memory Killer 选择一个进程杀掉以释放内存保证系统核心功能继续运行。1.1 OOM Killer 的评分机制OOM Killer 通过为每个进程计算一个 “坏蛋分数”OOM Score 来决定杀谁。分数越高越容易被选中。计算基于以下因素查看进程的 OOM 分数# 查看所有进程的 OOM 分数cat/proc/*/oom_score|sort-nr|head-20# 查看特定进程的分数cat/proc/1234/oom_score分数范围 0 到 1000实际上可能更高分数最高的进程被选为牺牲品。1.2 保护关键进程调整 oom_score_adj通过调整 /proc//oom_score_adj可以影响进程的 OOM 分数。示例保护 MySQL 不被 OOM Kill# 查找 MySQL 的 PIDpgrep mysqld# 假设 PID 为 1234# 设置 oom_score_adj 为 -1000禁止 OOM Killecho-1000/proc/1234/oom_score_adj# 验证cat/proc/1234/oom_score_adj在 systemd 服务中配置在服务单元文件中添加ini[Service]设置 OOM 保护值范围 -1000 到 1000OOMScoreAdjust-5001.3 OOM 日志分析当 OOM Killer 触发时内核会输出详细的日志到 dmesg 或 /var/log/messages。这是定位内存问题的第一手资料。查看 OOM 日志# 查看最近的内核 OOM 事件dmesg|grep-iout of memory-A20# 或从 systemd 日志中查找journalctl-k|grep-iout of memory-A20典型 OOM 日志解读[12345.678]Out of memory: Kill process9876(mysqld)score956or sacrifice child[12345.679]Killed process9876(mysqld)total-vm:12345678kB, anon-rss:8765432kB, file-rss:0kB[12345.680]oom_reaper: reaped process9876, now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB关键信息Kill process 9876 (mysqld)被杀的进程是 MySQL。score 956该进程的 OOM 分数极高。total-vm进程的总虚拟内存。anon-rss匿名内存进程私有内存的大小。如果日志中出现 MEMCG 字样说明是 Cgroups 层级触发的 OOM容器内存限制而非宿主机全局 OOM。二、内存碎片与回收机制2.1 内存碎片问题长期运行的系统会产生内存碎片——物理内存被分割成许多不连续的小块即使有足够的总空闲内存也可能无法分配一个大块的连续内存如需要 2MB 连续内存的大页。# 查看内存碎片情况cat/proc/buddyinfo输出示例textNode 0, zone Normal 1 2 4 8 16 32 64 128 256 512 1024数字从左到右表示空闲页块的大小2^order 页。如果左边的数字很大右边的数字为 0说明存在碎片化——有大量小内存块但没有大块连续内存。2.2 内存回收触发当内存低于阈值时内核会启动回收机制释放 Page Cache 和 Swap Cache。# 查看内存回收统计cat/proc/vmstat|grep-Epgscan|pgstealpgscan_kswapdkswapd 内核线程扫描的页数。pgsteal_kswapdkswapd 成功回收的页数。如果 pgscan 远大于 pgsteal说明回收效率低可能需要增加内存或调整 swappiness。三、Cgroups v2 内存限制在容器环境中内存限制通常通过 Cgroups v2 实现。相比 v1v2 提供了更一致的内存管理接口。3.1 Cgroups v2 内存核心文件3.2 容器内存 OOM 排查当容器触发 OOM 时可以用以下命令查看# 查看容器的内存限制和使用量cat/sys/fs/cgroup/memory.maxcat/sys/fs/cgroup/memory.currentcat/sys/fs/cgroup/memory.events# 查看 oom 事件计数如果 memory.events 中的 oom 计数不为 0说明该容器曾触发 OOM。3.3 Kubernetes 中的内存 OOMK8s 中 Pod 因内存超限被杀会显示 OOMKilled 状态kubectl describe pod my-pod输出中会包含textState: FailedReason: OOMKilled解决方案增加 Pod 的 memory.limits。排查应用内存泄漏使用 jmap、pprof 等工具。优化应用内存使用如减少缓存、调整 GC 参数。四、实战Java 应用内存泄漏排查Java 应用是内存问题的“重灾区”因为它运行在 JVM 中内存管理涉及 JVM 堆和操作系统内存两个层面。4.1 典型场景现象Java 应用的容器内存持续增长最终触发 OOMKilled。排查步骤第 1 步确认是否 JVM 堆内泄漏# 进入容器kubectlexec-itmy-pod -- /bin/bash# 使用 jcmd 查看堆使用情况jcmdpidGC.heap_info# 或使用 jstat 观察 GC 行为jstat-gcpid1s如果 GC.heap_info 显示 Old Gen 持续增长且 GC 无法回收说明堆内泄漏。第 2 步获取堆转储Heap Dump# 生成堆转储文件jcmdpidGC.heap_dump /tmp/heap.hprof# 或使用 jmapjmap -dump:live,formatb,file/tmp/heap.hprofpid第 3 步分析堆转储将 heap.hprof 下载到本地使用 MATMemory Analyzer Tool 或 JVisualVM 分析。MAT 的 Leak Suspects Report 可以直接告诉你哪个对象占用了最多的内存以及它的引用链。4.2 非堆内存泄漏Direct Memory / Native Memory如果堆内存正常但容器内存仍然不断增长可能是 Direct Memory 或 Native Memory 泄漏。Direct Memory使用 ByteBuffer.allocateDirect 分配的内存不受 JVM 堆大小限制。Native MemoryJNI 调用或底层 C 库分配的内存。排查 Native Memory 泄漏可以使用 pmap 观察进程的内存段变化或使用 jemalloc 的 profiling 功能。五、内存调优实践# 临时调整sudosysctlvm.swappiness10# 永久调整写入 /etc/sysctl.confvm.swappiness105.2 调整 overcommit 策略/proc/sys/vm/overcommit_memory 控制内核是否允许超额分配内存对于数据库服务器建议设置为 2严格限制避免因超额分配导致 OOM。echo2/proc/sys/vm/overcommit_memory配合 overcommit_ratio默认 50%总可分配内存为 物理内存 * overcommit_ratio / 100 Swap。5.3 使用 numactl 优化 NUMA 内存访问# 查看 NUMA 拓扑numactl--hardware# 将进程绑定到 NUMA 节点 0 的 CPU 和内存numactl--cpunodebind0--membind0./myapp六、小结OOM Killer 是内存耗尽的最终防线通过 oom_score 决定杀谁。用 oom_score_adj 保护关键进程。Cgroups v2 提供了容器的内存限制memory.max硬限制、memory.high软限制。内存碎片通过 /proc/buddyinfo 查看碎片严重会影响大页分配。Java 内存泄漏的常规定位路径确认堆内泄漏 → 获取堆转储 → 使用 MAT 分析。

相关新闻