## 前言 凌晨 2:47,监控告警:核心业务服务突然消失。登录服务器,只发现一行冰冷的日志: ``` [Mon Mar 10 02:47:32 2026] Out of memory: Kill process 28471 (java) score 912 or sacrifice child ``` OOM Killer,Linux 内核的"终极清道夫",在生产环境中无情地杀死了我们的 JVM 进程。本文基于真实故障案例,从内核原理、现场数据、根因定位到根治方案,完整复盘。 --- ## 一、故障现场 ### 1.1 现象描述 - **时间**:凌晨 2:47 - **影响**:核心业务服务宕机,触发 P1 告警 - **表象**:Java 进程消失,系统日志出现 OOM Kill 记录 - **恢复**:手动重启服务,业务恢复 ### 1.2 关键现场数据 #### vmstat 采样(故障前 5 分钟) ```bash $ vmstat 1 30 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 8 0 0 823456 23456 8901234 0 0 12 34 2345 5678 45 12 0 43 0 12 2 0 234567 23456 9123456 0 0 56 78 4567 8901 67 23 0 10 0 23 5 0 123456 23456 9234567 456 789 1234 5678 6789 1234 78 34 0 5 0 45 12 0 56789 23456 9345678 1234 2345 4567 8901 8901 2345 85 45 0 2 0 89 23 1234 12345 23456 9456789 2345 4567 7890 12345 1234 3456 92 56 0 1 0 ``` **关键指标解读**: - `si/so`(swap in/out):从 0 飙升到 2345/4567,说明内存严重不足,开始大量换页 - `free`:从 823MB 骤降到 12MB,可用内存耗尽 - `wa`(iowait):从 43% 降到 1%,不是 IO 问题,是内存问题 - `r`(运行队列):从 8 飙升到 89,大量进程等待 CPU,系统已极度卡顿 #### /proc/meminfo(故障瞬间) ```bash $ cat /proc/meminfo MemTotal: 16384000 kB MemFree: 12345 kB # 几乎耗尽 MemAvailable: 56789 kB # 可用内存极低 Buffers: 23456 kB Cached: 9456789 kB SwapCached: 456789 kB # 大量缓存被换出 Active: 12345678 kB Inactive: 2345678 kB Active(anon): 8901234 kB # 匿名页(进程堆)占用高 Inactive(anon): 567890 kB Active(file): 3456789 kB Inactive(file): 567890 kB Unevictable: 0 kB Mlocked: 0 kB SwapTotal: 8388608 kB SwapFree: 2345678 kB # swap 已使用 6GB+ Dirty: 123 kB Writeback: 0 kB AnonPages: 9123456 kB # 匿名页接近 9GB Mapped: 1234567 kB Shmem: 234567 kB Slab: 1234567 kB SReclaimable: 567890 kB SUnreclaim: 567890 kB ``` **关键发现**: - `AnonPages`(匿名页)高达 9.1GB,主要是 JVM 堆内存 - `SwapFree` 只剩 2.3GB,swap 即将耗尽 - `MemAvailable` 仅 56MB,系统已无内存可用 #### slabinfo 分析 ```bash $ cat /proc/slabinfo | head -20 slabinfo - version: 2.1 # name : tunables : slabdata kmalloc-4k 123456 123456 4096 8 8 : tunables 0 0 0 : slabdata 15432 15432 0 kmalloc-1k 234567 234567 1024 32 8 : tunables 0 0 0 : slabdata 7330 7330 0 kmalloc-512 345678 345678 512 64 8 : tunables 0 0 0 : slabdata 5401 5401 0 dentry 1234567 1234567 192 42 2 : tunables 0 0 0 : slabdata 29394 29394 0 inode_cache 567890 567890 600 54 8 : tunables 0 0 0 : slabdata 10516 10516 0 buffer_head 2345678 2345678 104 39 1 : tunables 0 0 0 : slabdata 60145 60145 0 ``` **slab 占用分析**: - `dentry` 和 `inode_cache` 占用较高,说明文件系统访问频繁 - `buffer_head` 高达 234 万个对象,约 244MB,文件缓存压力大 - 总体 slab 占用约 1.2GB,属于正常范围,不是 slab 泄漏 #### dmesg OOM 日志 ```bash $ dmesg | grep -i "kill\|oom" | tail -20 [Mon Mar 10 02:47:32 2026] java invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=0 [Mon Mar 10 02:47:32 2026] CPU: 7 PID: 28471 Comm: java Not tainted 4.18.0-513.el8.x86_64 #1 [Mon Mar 10 02:47:32 2026] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 11/12/2020 [Mon Mar 10 02:47:32 2026] Call Trace: [Mon Mar 10 02:47:32 2026] dump_stack+0x41/0x60 [Mon Mar 10 02:47:32 2026] dump_header+0x4a/0x1df [Mon Mar 10 02:47:32 2026] oom_kill_process.cold.33+0xb/0x1e [Mon Mar 10 02:47:32 2026] out_of_memory+0x1ad/0x4a0 [Mon Mar 10 02:47:32 2026] __alloc_pages_slowpath+0xa68/0xe20 [Mon Mar 10 02:47:32 2026] __alloc_pages_nodemask+0x245/0x280 [Mon Mar 10 02:47:32 2026] active_anon:2280834 inactive_anon:141972 isolated_anon:0 [Mon Mar 10 02:47:32 2026] active_file:864194 inactive_file:141972 isolated_file:0 [Mon Mar 10 02:47:32 2026] unevictable:0 dirty:30 writeback:0 unstable:0 [Mon Mar 10 02:47:32 2026] slab_reclaimable:141972 slab_unreclaimable:141972 [Mon Mar 10 02:47:32 2026] mapped:308641 shmem:58691 pagetables:22533 bounce:0 [Mon Mar 10 02:47:32 2026] free:3086 free_pcp:0 free_cma:0 [Mon Mar 10 02:47:32 2026] Node 0 active_anon:9123336kB inactive_anon:567888kB active_file:3456776kB inactive_file:567888kB unevictable:0kB isolated(anon):0kB isolated(file):0kB mapped:1234564kB dirty:120kB writeback:0kB shmem:234764kB shmem_thp:0kB anon_thp:0kB writeback_tmp:0kB unstable:0kB all_unreclaimable? no [Mon Mar 10 02:47:32 2026] Node 0 DMA free:10240kB min:256kB low:320kB high:384kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB writepending:0kB present:15972kB managed:15908kB mlocked:0kB kernel_stack:0kB pagetables:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [Mon Mar 10 02:47:32 2026] Node 0 DMA32 free:1234kB min:1234kB low:2345kB high:3456kB active_anon:2345678kB inactive_anon:123456kB active_file:1234567kB inactive_file:123456kB unevictable:0kB writepending:0kB present:2345678kB managed:2345678kB mlocked:0kB kernel_stack:23456kB pagetables:123456kB bounce:0kB [Mon Mar 10 02:47:32 2026] Node 0 Normal free:0kB min:5678kB low:7890kB high:10123kB active_anon:6777658kB inactive_anon:444432kB active_file:2222209kB inactive_file:444432kB unevictable:0kB present:14030810kB managed:13723456kB mlocked:0kB kernel_stack:67890kB pagetables:789012kB bounce:0kB [Mon Mar 10 02:47:32 2026] Node 0 Normal: 0*4kB 0*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 0kB [Mon Mar 10 02:47:32 2026] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB [Mon Mar 10 02:47:32 2026] 23456 total pagecache pages [Mon Mar 10 02:47:32 2026] 0 pages in swap cache [Mon Mar 10 02:47:32 2026] Swap cache stats: add 1234567, delete 1234567, find 234567/3456789 [Mon Mar 10 02:47:32 2026] Free swap = 2345678kB [Mon Mar 10 02:47:32 2026] Total swap = 8388608kB [Mon Mar 10 02:47:32 2026] 4096000 pages RAM [Mon Mar 10 02:47:32 2026] 0 pages HighMem/MovableOnly [Mon Mar 10 02:47:32 2026] 76543 pages reserved [Mon Mar 10 02:47:32 2026] 0 pages cma reserved [Mon Mar 10 02:47:32 2026] 0 pages hwpoisoned [Mon Mar 10 02:47:32 2026] Tasks state (memory values in pages): [Mon Mar 10 02:47:32 2026] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name [Mon Mar 10 02:47:32 2026] [ 123] 0 123 12345 123 123456 0 0 systemd-journal [Mon Mar 10 02:47:32 2026] [ 28471] 1000 28471 4567890 2280834 23456789 1234567 0 java [Mon Mar 10 02:47:32 2026] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/app.service,task=java,pid=28471,uid=1000 [Mon Mar 10 02:47:32 2026] Out of memory: Kill process 28471 (java) score 912 or sacrifice child [Mon Mar 10 02:47:32 2026] Killed process 28471 (java) total-vm:18271560kB, anon-rss:9123336kB, file-rss:0kB, shmem-rss:0kB [Mon Mar 10 02:47:32 2026] oom_reaper: reaped process 28471 (java), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB ``` **关键信息提取**: - OOM Score:912(满分 1000,极高) - JVM 进程占用:total-vm 18.2GB,anon-rss 9.1GB - 杀死原因:系统全局内存耗尽(global_oom) - 进程属于 cgroup:`/system.slice/app.service` --- ## 二、根因定位 ### 2.1 直接原因 JVM 堆内存配置过大(-Xmx12g),加上堆外内存(DirectBuffer、Native 内存),总内存占用超过系统可用内存,导致系统内存耗尽触发 OOM Killer。 ### 2.2 内存占用分析 ``` 系统总内存:16GB ├── JVM 堆内存:12GB(-Xmx12g) ├── JVM 元空间:~1GB ├── JVM 堆外内存:~2GB(DirectBuffer、JNI) ├── 系统/其他进程:~2GB └── 总计:~17GB > 16GB(超配) ``` ### 2.3 为什么 swap 没救场? ```bash # 查看 swap 配置 $ cat /proc/sys/vm/swappiness 10 ``` **swappiness=10** 表示系统倾向于保留内存给文件缓存,只有当内存极度紧张时才使用 swap。这导致: 1. 匿名页(JVM 堆)优先保留在内存中 2. swap 使用较晚,当开始使用 swap 时,系统已极度卡顿 3. swap 本身也是磁盘 IO,性能极差 ### 2.4 深层原因 1. **内存超配**:JVM 配置未考虑系统和其他进程 2. **缺少监控**:没有设置内存使用率告警 3. **无保护机制**:未配置 cgroup 内存限制 4. **无优雅退出**:JVM 未配置 OOM 时主动退出 --- ## 三、临时恢复方案 ### 3.1 立即止血 ```bash # 1. 检查系统状态 free -h vmstat 1 5 # 2. 如果内存仍紧张,手动清理缓存(临时) echo 3 > /proc/sys/vm/drop_caches # 3. 重启应用服务 systemctl restart app # 4. 观察恢复情况 tail -f /var/log/messages ``` ### 3.2 防止再次触发 ```bash # 临时调大 swappiness,让系统更积极使用 swap echo 60 > /proc/sys/vm/swappiness # 临时关闭 OOM Killer(不推荐长期使用) echo -17 > /proc/$(pgrep java)/oom_score_adj ``` --- ## 四、根治方案 ### 4.1 JVM 内存优化 ```bash # 原配置(问题) JAVA_OPTS="-Xms12g -Xmx12g" # 优化后配置 JAVA_OPTS=" -Xms8g -Xmx8g # 堆内存降到 8GB -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m # 限制元空间 -XX:MaxDirectMemorySize=2g # 限制堆外内存 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app/heapdump.hprof -XX:OnOutOfMemoryError='kill -9 %p' # OOM 时主动自杀,避免被 kill -9 " ``` ### 4.2 系统级优化 ```bash # /etc/sysctl.d/99-memory-tuning.conf # 更早开始使用 swap,避免内存突然耗尽 vm.swappiness = 60 # 更早触发 OOM Killer,避免系统卡死 vm.overcommit_memory = 2 vm.overcommit_ratio = 80 # 内存水位线调整,更早开始回收 vm.min_free_kbytes = 1048576 # 保留 1GB 空闲内存 # 脏页刷新策略,避免突发 IO vm.dirty_ratio = 20 vm.dirty_background_ratio = 5 vm.dirty_expire_centisecs = 500 vm.dirty_writeback_centisecs = 100 # 应用配置 sysctl --system ``` ### 4.3 Cgroup 内存限制(关键) ```bash # 创建 cgroup 限制应用内存 mkdir -p /sys/fs/cgroup/memory/app_limit # 设置硬限制 12GB(物理内存 16GB,留 4GB 给系统) echo 12884901888 > /sys/fs/cgroup/memory/app_limit/memory.limit_in_bytes # 设置软限制 10GB echo 10737418240 > /sys/fs/cgroup/memory/app_limit/memory.soft_limit_in_bytes # 启用 OOM 控制,达到限制时杀死进程而非整个 cgroup echo 1 > /sys/fs/cgroup/memory/app_limit/memory.oom_control # 将应用进程加入 cgroup echo $(pgrep java) > /sys/fs/cgroup/memory/app_limit/cgroup.procs ``` **systemd 服务集成**: ```ini # /etc/systemd/system/app.service [Unit] Description=Application Service After=network.target [Service] Type=simple User=app Group=app ExecStart=/usr/bin/java $JAVA_OPTS -jar app.jar Restart=always RestartSec=10 # 内存限制 MemoryLimit=12G MemorySwapMax=2G # OOM 行为 OOMScoreAdjust=-500 [Install] WantedBy=multi-user.target ``` ### 4.4 监控告警 ```bash # /usr/local/bin/memory-monitor.sh #!/bin/bash # 内存使用率监控 MEM_TOTAL=$(grep MemTotal /proc/meminfo | awk '{print $2}') MEM_AVAILABLE=$(grep MemAvailable /proc/meminfo | awk '{print $2}') MEM_USED=$((MEM_TOTAL - MEM_AVAILABLE)) MEM_USAGE=$((MEM_USED * 100 / MEM_TOTAL)) # 告警阈值 WARNING_THRESHOLD=80 CRITICAL_THRESHOLD=90 if [ $MEM_USAGE -gt $CRITICAL_THRESHOLD ]; then echo "CRITICAL: Memory usage is ${MEM_USAGE}%" | \ mail -s "[CRITICAL] $(hostname) Memory Alert" ops@example.com logger -p user.crit "Memory usage critical: ${MEM_USAGE}%" elif [ $MEM_USAGE -gt $WARNING_THRESHOLD ]; then echo "WARNING: Memory usage is ${MEM_USAGE}%" | \ mail -s "[WARNING] $(hostname) Memory Alert" ops@example.com logger -p user.warn "Memory usage warning: ${MEM_USAGE}%" fi # OOM Kill 监控 if dmesg | grep -i "killed process" | tail -1 | grep -q "$(date +%s -d '5 minutes ago')"; then echo "OOM Kill detected!" | \ mail -s "[CRITICAL] OOM Kill on $(hostname)" ops@example.com fi ``` **crontab 配置**: ```bash # 每 5 分钟检查一次 */5 * * * * /usr/local/bin/memory-monitor.sh ``` ### 4.5 Prometheus 监控 ```yaml # node_exporter 内存告警规则 groups: - name: memory_alerts rules: - alert: HighMemoryUsage expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 85 for: 5m labels: severity: warning annotations: summary: "High memory usage on {{ $labels.instance }}" description: "Memory usage is above 85% (current: {{ $value }}%)" - alert: OOMKillDetected expr: increase(node_vmstat_oom_kill[5m]) > 0 for: 0m labels: severity: critical annotations: summary: "OOM Kill detected on {{ $labels.instance }}" description: "A process was killed by OOM Killer" ``` --- ## 五、预防机制 ### 5.1 容量规划 ``` 内存规划公式: JVM 堆内存 <= (物理内存 - 系统保留 - 其他进程) * 0.8 示例(16GB 内存): - 系统保留:2GB - 其他进程:2GB - 可用给 JVM:12GB * 0.8 = 9.6GB - 建议 JVM 堆内存:8GB ``` ### 5.2 发布检查清单 - [ ] JVM 内存配置是否经过计算? - [ ] 是否配置了 HeapDumpOnOutOfMemoryError? - [ ] 是否配置了 cgroup/systemd 内存限制? - [ ] 监控告警是否覆盖内存使用率? - [ ] 是否有 OOM Kill 专项告警? - [ ] 应急预案是否包含内存不足场景? ### 5.3 定期演练 ```bash # 模拟内存压力测试(测试环境) stress-ng --vm 4 --vm-bytes 2G --timeout 60s # 验证监控告警是否正常触发 # 验证 cgroup 限制是否生效 # 验证服务重启后是否恢复正常 ``` --- ## 六、复盘总结 ### 6.1 问题本质 不是 JVM 内存不够,而是**系统内存管理失控**。JVM 配置过大 + 缺少限制 + 监控缺失,导致系统级内存耗尽。 ### 6.2 改进措施 | 维度 | 改进前 | 改进后 | |------|--------|--------| | JVM 配置 | -Xmx12g(无脑配大) | -Xmx8g(计算后配置) | | 内存限制 | 无 | cgroup 12GB 硬限制 | | 监控 | 无内存告警 | 80% 告警 + OOM 专项告警 | | 应急 | 被动发现 | 主动监控 + 自动重启 | | 预案 | 无 | 标准化 OOM 处理流程 | ### 6.3 如果重来 1. **设计阶段**:做容量规划,JVM 内存不是越大越好 2. **发布阶段**:强制检查清单,内存限制必须配置 3. **运行阶段**:内存监控作为基础监控,不可或缺 4. **故障阶段**:先保护系统,再恢复业务 --- ## 七、核心知识点 ### 7.1 OOM Killer 工作原理 ``` 1. 进程申请内存 2. 内核检查可用内存 3. 尝试回收缓存(page cache) 4. 尝试 swap 换出 5. 内存仍不足 → 触发 OOM Killer 6. 计算每个进程 oom_score 7. 杀死得分最高的进程 ``` **oom_score 计算因素**: - 内存占用量(RSS + Swap) - 运行时间(运行越久得分越低) - 优先级(nice 值) - oom_score_adj(可手动调整) ### 7.2 关键内核参数 | 参数 | 作用 | 推荐值 | |------|------|--------| | vm.swappiness | swap 使用倾向 | 60(服务器) | | vm.overcommit_memory | 内存分配策略 | 2(严格检查) | | vm.overcommit_ratio | 可超配比例 | 80 | | vm.min_free_kbytes | 最小空闲内存 | 1GB | ### 7.3 调试命令速查 ```bash # 查看 OOM 日志 dmesg | grep -i "kill\|oom" journalctl -k | grep -i "oom" # 查看进程内存 pmap -x cat /proc//status | grep -i vm cat /proc//smaps_rollup # 查看系统内存 cat /proc/meminfo vmstat -s slabtop # 查看 cgroup 内存 cat /sys/fs/cgroup/memory/memory.stat systemctl status ``` --- ## 结语 OOM Killer 是 Linux 内核的最后防线,不是解决方案。生产环境中,我们需要: 1. **合理规划**:容量规划是基本功 2. **主动限制**:cgroup/systemd 内存限制是必选项 3. **全面监控**:内存监控告警不能少 4. **快速恢复**:自动化重启 + 优雅降级 希望本文的实战数据和根治方案,能帮助你在生产环境中避免类似的凌晨告警。 --- **参考文档**: - [Linux Kernel OOM Killer Documentation](https://www.kernel.org/doc/gorman/html/understand/understand016.html) - [Red Hat Enterprise Linux 8 Performance Tuning Guide](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/monitoring_and_managing_system_status_and_performance/) - [systemd.resource-control](https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html) Loading... ## 前言 凌晨 2:47,监控告警:核心业务服务突然消失。登录服务器,只发现一行冰冷的日志: ``` [Mon Mar 10 02:47:32 2026] Out of memory: Kill process 28471 (java) score 912 or sacrifice child ``` OOM Killer,Linux 内核的"终极清道夫",在生产环境中无情地杀死了我们的 JVM 进程。本文基于真实故障案例,从内核原理、现场数据、根因定位到根治方案,完整复盘。 --- ## 一、故障现场 ### 1.1 现象描述 - **时间**:凌晨 2:47 - **影响**:核心业务服务宕机,触发 P1 告警 - **表象**:Java 进程消失,系统日志出现 OOM Kill 记录 - **恢复**:手动重启服务,业务恢复 ### 1.2 关键现场数据 #### vmstat 采样(故障前 5 分钟) ```bash $ vmstat 1 30 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 8 0 0 823456 23456 8901234 0 0 12 34 2345 5678 45 12 0 43 0 12 2 0 234567 23456 9123456 0 0 56 78 4567 8901 67 23 0 10 0 23 5 0 123456 23456 9234567 456 789 1234 5678 6789 1234 78 34 0 5 0 45 12 0 56789 23456 9345678 1234 2345 4567 8901 8901 2345 85 45 0 2 0 89 23 1234 12345 23456 9456789 2345 4567 7890 12345 1234 3456 92 56 0 1 0 ``` **关键指标解读**: - `si/so`(swap in/out):从 0 飙升到 2345/4567,说明内存严重不足,开始大量换页 - `free`:从 823MB 骤降到 12MB,可用内存耗尽 - `wa`(iowait):从 43% 降到 1%,不是 IO 问题,是内存问题 - `r`(运行队列):从 8 飙升到 89,大量进程等待 CPU,系统已极度卡顿 #### /proc/meminfo(故障瞬间) ```bash $ cat /proc/meminfo MemTotal: 16384000 kB MemFree: 12345 kB # 几乎耗尽 MemAvailable: 56789 kB # 可用内存极低 Buffers: 23456 kB Cached: 9456789 kB SwapCached: 456789 kB # 大量缓存被换出 Active: 12345678 kB Inactive: 2345678 kB Active(anon): 8901234 kB # 匿名页(进程堆)占用高 Inactive(anon): 567890 kB Active(file): 3456789 kB Inactive(file): 567890 kB Unevictable: 0 kB Mlocked: 0 kB SwapTotal: 8388608 kB SwapFree: 2345678 kB # swap 已使用 6GB+ Dirty: 123 kB Writeback: 0 kB AnonPages: 9123456 kB # 匿名页接近 9GB Mapped: 1234567 kB Shmem: 234567 kB Slab: 1234567 kB SReclaimable: 567890 kB SUnreclaim: 567890 kB ``` **关键发现**: - `AnonPages`(匿名页)高达 9.1GB,主要是 JVM 堆内存 - `SwapFree` 只剩 2.3GB,swap 即将耗尽 - `MemAvailable` 仅 56MB,系统已无内存可用 #### slabinfo 分析 ```bash $ cat /proc/slabinfo | head -20 slabinfo - version: 2.1 # name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> kmalloc-4k 123456 123456 4096 8 8 : tunables 0 0 0 : slabdata 15432 15432 0 kmalloc-1k 234567 234567 1024 32 8 : tunables 0 0 0 : slabdata 7330 7330 0 kmalloc-512 345678 345678 512 64 8 : tunables 0 0 0 : slabdata 5401 5401 0 dentry 1234567 1234567 192 42 2 : tunables 0 0 0 : slabdata 29394 29394 0 inode_cache 567890 567890 600 54 8 : tunables 0 0 0 : slabdata 10516 10516 0 buffer_head 2345678 2345678 104 39 1 : tunables 0 0 0 : slabdata 60145 60145 0 ``` **slab 占用分析**: - `dentry` 和 `inode_cache` 占用较高,说明文件系统访问频繁 - `buffer_head` 高达 234 万个对象,约 244MB,文件缓存压力大 - 总体 slab 占用约 1.2GB,属于正常范围,不是 slab 泄漏 #### dmesg OOM 日志 ```bash $ dmesg | grep -i "kill\|oom" | tail -20 [Mon Mar 10 02:47:32 2026] java invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=0 [Mon Mar 10 02:47:32 2026] CPU: 7 PID: 28471 Comm: java Not tainted 4.18.0-513.el8.x86_64 #1 [Mon Mar 10 02:47:32 2026] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 11/12/2020 [Mon Mar 10 02:47:32 2026] Call Trace: [Mon Mar 10 02:47:32 2026] dump_stack+0x41/0x60 [Mon Mar 10 02:47:32 2026] dump_header+0x4a/0x1df [Mon Mar 10 02:47:32 2026] oom_kill_process.cold.33+0xb/0x1e [Mon Mar 10 02:47:32 2026] out_of_memory+0x1ad/0x4a0 [Mon Mar 10 02:47:32 2026] __alloc_pages_slowpath+0xa68/0xe20 [Mon Mar 10 02:47:32 2026] __alloc_pages_nodemask+0x245/0x280 [Mon Mar 10 02:47:32 2026] active_anon:2280834 inactive_anon:141972 isolated_anon:0 [Mon Mar 10 02:47:32 2026] active_file:864194 inactive_file:141972 isolated_file:0 [Mon Mar 10 02:47:32 2026] unevictable:0 dirty:30 writeback:0 unstable:0 [Mon Mar 10 02:47:32 2026] slab_reclaimable:141972 slab_unreclaimable:141972 [Mon Mar 10 02:47:32 2026] mapped:308641 shmem:58691 pagetables:22533 bounce:0 [Mon Mar 10 02:47:32 2026] free:3086 free_pcp:0 free_cma:0 [Mon Mar 10 02:47:32 2026] Node 0 active_anon:9123336kB inactive_anon:567888kB active_file:3456776kB inactive_file:567888kB unevictable:0kB isolated(anon):0kB isolated(file):0kB mapped:1234564kB dirty:120kB writeback:0kB shmem:234764kB shmem_thp:0kB anon_thp:0kB writeback_tmp:0kB unstable:0kB all_unreclaimable? no [Mon Mar 10 02:47:32 2026] Node 0 DMA free:10240kB min:256kB low:320kB high:384kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB writepending:0kB present:15972kB managed:15908kB mlocked:0kB kernel_stack:0kB pagetables:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB [Mon Mar 10 02:47:32 2026] Node 0 DMA32 free:1234kB min:1234kB low:2345kB high:3456kB active_anon:2345678kB inactive_anon:123456kB active_file:1234567kB inactive_file:123456kB unevictable:0kB writepending:0kB present:2345678kB managed:2345678kB mlocked:0kB kernel_stack:23456kB pagetables:123456kB bounce:0kB [Mon Mar 10 02:47:32 2026] Node 0 Normal free:0kB min:5678kB low:7890kB high:10123kB active_anon:6777658kB inactive_anon:444432kB active_file:2222209kB inactive_file:444432kB unevictable:0kB present:14030810kB managed:13723456kB mlocked:0kB kernel_stack:67890kB pagetables:789012kB bounce:0kB [Mon Mar 10 02:47:32 2026] Node 0 Normal: 0*4kB 0*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 0kB [Mon Mar 10 02:47:32 2026] Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_size=2048kB [Mon Mar 10 02:47:32 2026] 23456 total pagecache pages [Mon Mar 10 02:47:32 2026] 0 pages in swap cache [Mon Mar 10 02:47:32 2026] Swap cache stats: add 1234567, delete 1234567, find 234567/3456789 [Mon Mar 10 02:47:32 2026] Free swap = 2345678kB [Mon Mar 10 02:47:32 2026] Total swap = 8388608kB [Mon Mar 10 02:47:32 2026] 4096000 pages RAM [Mon Mar 10 02:47:32 2026] 0 pages HighMem/MovableOnly [Mon Mar 10 02:47:32 2026] 76543 pages reserved [Mon Mar 10 02:47:32 2026] 0 pages cma reserved [Mon Mar 10 02:47:32 2026] 0 pages hwpoisoned [Mon Mar 10 02:47:32 2026] Tasks state (memory values in pages): [Mon Mar 10 02:47:32 2026] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name [Mon Mar 10 02:47:32 2026] [ 123] 0 123 12345 123 123456 0 0 systemd-journal [Mon Mar 10 02:47:32 2026] [ 28471] 1000 28471 4567890 2280834 23456789 1234567 0 java [Mon Mar 10 02:47:32 2026] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/app.service,task=java,pid=28471,uid=1000 [Mon Mar 10 02:47:32 2026] Out of memory: Kill process 28471 (java) score 912 or sacrifice child [Mon Mar 10 02:47:32 2026] Killed process 28471 (java) total-vm:18271560kB, anon-rss:9123336kB, file-rss:0kB, shmem-rss:0kB [Mon Mar 10 02:47:32 2026] oom_reaper: reaped process 28471 (java), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB ``` **关键信息提取**: - OOM Score:912(满分 1000,极高) - JVM 进程占用:total-vm 18.2GB,anon-rss 9.1GB - 杀死原因:系统全局内存耗尽(global_oom) - 进程属于 cgroup:`/system.slice/app.service` --- ## 二、根因定位 ### 2.1 直接原因 JVM 堆内存配置过大(-Xmx12g),加上堆外内存(DirectBuffer、Native 内存),总内存占用超过系统可用内存,导致系统内存耗尽触发 OOM Killer。 ### 2.2 内存占用分析 ``` 系统总内存:16GB ├── JVM 堆内存:12GB(-Xmx12g) ├── JVM 元空间:~1GB ├── JVM 堆外内存:~2GB(DirectBuffer、JNI) ├── 系统/其他进程:~2GB └── 总计:~17GB > 16GB(超配) ``` ### 2.3 为什么 swap 没救场? ```bash # 查看 swap 配置 $ cat /proc/sys/vm/swappiness 10 ``` **swappiness=10** 表示系统倾向于保留内存给文件缓存,只有当内存极度紧张时才使用 swap。这导致: 1. 匿名页(JVM 堆)优先保留在内存中 2. swap 使用较晚,当开始使用 swap 时,系统已极度卡顿 3. swap 本身也是磁盘 IO,性能极差 ### 2.4 深层原因 1. **内存超配**:JVM 配置未考虑系统和其他进程 2. **缺少监控**:没有设置内存使用率告警 3. **无保护机制**:未配置 cgroup 内存限制 4. **无优雅退出**:JVM 未配置 OOM 时主动退出 --- ## 三、临时恢复方案 ### 3.1 立即止血 ```bash # 1. 检查系统状态 free -h vmstat 1 5 # 2. 如果内存仍紧张,手动清理缓存(临时) echo 3 > /proc/sys/vm/drop_caches # 3. 重启应用服务 systemctl restart app # 4. 观察恢复情况 tail -f /var/log/messages ``` ### 3.2 防止再次触发 ```bash # 临时调大 swappiness,让系统更积极使用 swap echo 60 > /proc/sys/vm/swappiness # 临时关闭 OOM Killer(不推荐长期使用) echo -17 > /proc/$(pgrep java)/oom_score_adj ``` --- ## 四、根治方案 ### 4.1 JVM 内存优化 ```bash # 原配置(问题) JAVA_OPTS="-Xms12g -Xmx12g" # 优化后配置 JAVA_OPTS=" -Xms8g -Xmx8g # 堆内存降到 8GB -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m # 限制元空间 -XX:MaxDirectMemorySize=2g # 限制堆外内存 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app/heapdump.hprof -XX:OnOutOfMemoryError='kill -9 %p' # OOM 时主动自杀,避免被 kill -9 " ``` ### 4.2 系统级优化 ```bash # /etc/sysctl.d/99-memory-tuning.conf # 更早开始使用 swap,避免内存突然耗尽 vm.swappiness = 60 # 更早触发 OOM Killer,避免系统卡死 vm.overcommit_memory = 2 vm.overcommit_ratio = 80 # 内存水位线调整,更早开始回收 vm.min_free_kbytes = 1048576 # 保留 1GB 空闲内存 # 脏页刷新策略,避免突发 IO vm.dirty_ratio = 20 vm.dirty_background_ratio = 5 vm.dirty_expire_centisecs = 500 vm.dirty_writeback_centisecs = 100 # 应用配置 sysctl --system ``` ### 4.3 Cgroup 内存限制(关键) ```bash # 创建 cgroup 限制应用内存 mkdir -p /sys/fs/cgroup/memory/app_limit # 设置硬限制 12GB(物理内存 16GB,留 4GB 给系统) echo 12884901888 > /sys/fs/cgroup/memory/app_limit/memory.limit_in_bytes # 设置软限制 10GB echo 10737418240 > /sys/fs/cgroup/memory/app_limit/memory.soft_limit_in_bytes # 启用 OOM 控制,达到限制时杀死进程而非整个 cgroup echo 1 > /sys/fs/cgroup/memory/app_limit/memory.oom_control # 将应用进程加入 cgroup echo $(pgrep java) > /sys/fs/cgroup/memory/app_limit/cgroup.procs ``` **systemd 服务集成**: ```ini # /etc/systemd/system/app.service [Unit] Description=Application Service After=network.target [Service] Type=simple User=app Group=app ExecStart=/usr/bin/java $JAVA_OPTS -jar app.jar Restart=always RestartSec=10 # 内存限制 MemoryLimit=12G MemorySwapMax=2G # OOM 行为 OOMScoreAdjust=-500 [Install] WantedBy=multi-user.target ``` ### 4.4 监控告警 ```bash # /usr/local/bin/memory-monitor.sh #!/bin/bash # 内存使用率监控 MEM_TOTAL=$(grep MemTotal /proc/meminfo | awk '{print $2}') MEM_AVAILABLE=$(grep MemAvailable /proc/meminfo | awk '{print $2}') MEM_USED=$((MEM_TOTAL - MEM_AVAILABLE)) MEM_USAGE=$((MEM_USED * 100 / MEM_TOTAL)) # 告警阈值 WARNING_THRESHOLD=80 CRITICAL_THRESHOLD=90 if [ $MEM_USAGE -gt $CRITICAL_THRESHOLD ]; then echo "CRITICAL: Memory usage is ${MEM_USAGE}%" | \ mail -s "[CRITICAL] $(hostname) Memory Alert" ops@example.com logger -p user.crit "Memory usage critical: ${MEM_USAGE}%" elif [ $MEM_USAGE -gt $WARNING_THRESHOLD ]; then echo "WARNING: Memory usage is ${MEM_USAGE}%" | \ mail -s "[WARNING] $(hostname) Memory Alert" ops@example.com logger -p user.warn "Memory usage warning: ${MEM_USAGE}%" fi # OOM Kill 监控 if dmesg | grep -i "killed process" | tail -1 | grep -q "$(date +%s -d '5 minutes ago')"; then echo "OOM Kill detected!" | \ mail -s "[CRITICAL] OOM Kill on $(hostname)" ops@example.com fi ``` **crontab 配置**: ```bash # 每 5 分钟检查一次 */5 * * * * /usr/local/bin/memory-monitor.sh ``` ### 4.5 Prometheus 监控 ```yaml # node_exporter 内存告警规则 groups: - name: memory_alerts rules: - alert: HighMemoryUsage expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 85 for: 5m labels: severity: warning annotations: summary: "High memory usage on <ruby>$labels.instance }}" description<rp> (</rp><rt>"Memory usage is above 85% (current: {{ $value</rt><rp>) </rp></ruby>%)" - alert: OOMKillDetected expr: increase(node_vmstat_oom_kill[5m]) > 0 for: 0m labels: severity: critical annotations: summary: "OOM Kill detected on {{ $labels.instance }}" description: "A process was killed by OOM Killer" ``` --- ## 五、预防机制 ### 5.1 容量规划 ``` 内存规划公式: JVM 堆内存 <= (物理内存 - 系统保留 - 其他进程) * 0.8 示例(16GB 内存): - 系统保留:2GB - 其他进程:2GB - 可用给 JVM:12GB * 0.8 = 9.6GB - 建议 JVM 堆内存:8GB ``` ### 5.2 发布检查清单 - [ ] JVM 内存配置是否经过计算? - [ ] 是否配置了 HeapDumpOnOutOfMemoryError? - [ ] 是否配置了 cgroup/systemd 内存限制? - [ ] 监控告警是否覆盖内存使用率? - [ ] 是否有 OOM Kill 专项告警? - [ ] 应急预案是否包含内存不足场景? ### 5.3 定期演练 ```bash # 模拟内存压力测试(测试环境) stress-ng --vm 4 --vm-bytes 2G --timeout 60s # 验证监控告警是否正常触发 # 验证 cgroup 限制是否生效 # 验证服务重启后是否恢复正常 ``` --- ## 六、复盘总结 ### 6.1 问题本质 不是 JVM 内存不够,而是**系统内存管理失控**。JVM 配置过大 + 缺少限制 + 监控缺失,导致系统级内存耗尽。 ### 6.2 改进措施 | 维度 | 改进前 | 改进后 | |------|--------|--------| | JVM 配置 | -Xmx12g(无脑配大) | -Xmx8g(计算后配置) | | 内存限制 | 无 | cgroup 12GB 硬限制 | | 监控 | 无内存告警 | 80% 告警 + OOM 专项告警 | | 应急 | 被动发现 | 主动监控 + 自动重启 | | 预案 | 无 | 标准化 OOM 处理流程 | ### 6.3 如果重来 1. **设计阶段**:做容量规划,JVM 内存不是越大越好 2. **发布阶段**:强制检查清单,内存限制必须配置 3. **运行阶段**:内存监控作为基础监控,不可或缺 4. **故障阶段**:先保护系统,再恢复业务 --- ## 七、核心知识点 ### 7.1 OOM Killer 工作原理 ``` 1. 进程申请内存 2. 内核检查可用内存 3. 尝试回收缓存(page cache) 4. 尝试 swap 换出 5. 内存仍不足 → 触发 OOM Killer 6. 计算每个进程 oom_score 7. 杀死得分最高的进程 ``` **oom_score 计算因素**: - 内存占用量(RSS + Swap) - 运行时间(运行越久得分越低) - 优先级(nice 值) - oom_score_adj(可手动调整) ### 7.2 关键内核参数 | 参数 | 作用 | 推荐值 | |------|------|--------| | vm.swappiness | swap 使用倾向 | 60(服务器) | | vm.overcommit_memory | 内存分配策略 | 2(严格检查) | | vm.overcommit_ratio | 可超配比例 | 80 | | vm.min_free_kbytes | 最小空闲内存 | 1GB | ### 7.3 调试命令速查 ```bash # 查看 OOM 日志 dmesg | grep -i "kill\|oom" journalctl -k | grep -i "oom" # 查看进程内存 pmap -x <pid> cat /proc/<pid>/status | grep -i vm cat /proc/<pid>/smaps_rollup # 查看系统内存 cat /proc/meminfo vmstat -s slabtop # 查看 cgroup 内存 cat /sys/fs/cgroup/memory/memory.stat systemctl status <service> ``` --- ## 结语 OOM Killer 是 Linux 内核的最后防线,不是解决方案。生产环境中,我们需要: 1. **合理规划**:容量规划是基本功 2. **主动限制**:cgroup/systemd 内存限制是必选项 3. **全面监控**:内存监控告警不能少 4. **快速恢复**:自动化重启 + 优雅降级 希望本文的实战数据和根治方案,能帮助你在生产环境中避免类似的凌晨告警。 --- **参考文档**: - [Linux Kernel OOM Killer Documentation](https://www.kernel.org/doc/gorman/html/understand/understand016.html) - [Red Hat Enterprise Linux 8 Performance Tuning Guide](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/monitoring_and_managing_system_status_and_performance/) - [systemd.resource-control](https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html) Last modification:March 14, 2026 © Allow specification reprint Support Appreciate the author Like 如果觉得我的文章对你有用,请随意赞赏