4、Linux系统故障(上):CPU飙升100%排查、内存泄漏(OOM Killer)分析与解决

说实话,做运维这些年,最怕半夜被报警电话吵醒。而CPU飙到100%、内存被OOM Killer干掉进程,这俩问题绝对能排进「运维噩梦Top 3」。今天咱们就聊聊这两个硬骨头怎么啃。

核心知识点速览

  • CPU飙升100%的定位思路:从top到perf的完整链路
  • 内存泄漏的本质:谁吃了内存不吐骨头
  • OOM Killer的触发机制与日志分析
  • 实战案例:一次Java进程被OOM Kill的完整复盘

一、CPU飙升100%:别慌,先看谁在搞事

CPU飙到100%,说白了就是某个进程把CPU时间片全占了。我个人的习惯是,先跑top命令看一眼全局。

# 第一件事:看哪个进程最嚣张
top -c

# 按P键按CPU使用率排序
# 按M键按内存使用率排序

找到那个CPU占用最高的进程后,记下它的PID。接下来要深入看看它到底在干什么。

1.1 用strace看系统调用

strace是个好东西,它能告诉你进程在跟内核要什么资源。我曾经遇到过一个案例,某个Java进程CPU飙到800%,用strace一看,发现它在疯狂做lseek系统调用——说白了就是在磁盘上反复定位,典型的日志轮转配置不当。

# 跟踪进程的系统调用(别跑太久,几秒就够了)
strace -p <PID> -c -S time 2>&1 | head -20

1.2 用perf做火焰图

如果strace看不出名堂,那就上perf。perf能采样进程的调用栈,生成火焰图。你想想看,火焰图就像一张热力图,越宽的函数调用栈说明它占CPU越多。

# 采样30秒
perf record -p <PID> -g -- sleep 30

# 生成火焰图数据
perf script > out.perf

# 用FlameGraph工具生成SVG
./stackcollapse-perf.pl out.perf > out.folded
./flamegraph.pl out.folded > cpu.svg

我的小技巧: 生产环境上别直接装perf,很多安全策略不允许。我一般用perf stat做轻量级采样,或者用cat /proc/PID/status看线程数,也能快速定位问题。

二、内存泄漏:谁吃了内存不吐骨头

内存泄漏,说白了就是程序申请了内存,用完不还。一次两次没事,但日积月累,内存就被吃光了。这时候OOM Killer就登场了。

为什么会这样?我遇到过最典型的场景:Java应用里有个全局的HashMap,每次请求都往里塞数据,但从来不清理。一周后,这个Map占了8GB内存,然后OOM了。

2.1 怎么判断是不是内存泄漏

用free -m看内存使用趋势,如果used持续增长,buff/cache没变化,那基本就是泄漏了。

# 每5秒看一次内存变化
watch -n 5 'free -m | grep -E "Mem|Swap"'

更专业的做法是用/proc/meminfo看详细指标:

cat /proc/meminfo | grep -E "MemTotal|MemFree|Cached|Slab"

2.2 定位泄漏源

对于C/C++程序,我用valgrind做内存检测。对于Java程序,我习惯用jmap做堆转储,然后用MAT分析。

# Java堆转储
jmap -dump:live,format=b,file=heap.hprof <PID>

# 用MAT打开heap.hprof,看哪些对象占内存最多

注意: jmap -dump会触发Full GC,生产环境慎用。我一般会在低峰期操作,或者用jcmd替代。

三、OOM Killer:系统最后的暴力手段

当系统内存耗尽,内核会启动OOM Killer,挑一个进程杀掉。它怎么挑?看一个叫oom_score的分数,分数越高越容易被杀。

3.1 OOM Killer的日志在哪

OOM Killer的日志在/var/log/messages或/var/log/syslog里。用dmesg也能看到。

# 查看OOM Killer日志
dmesg | grep -i "killed process"

# 或者
grep -i "oom" /var/log/messages

日志长这样:

[123456.789] oom-killer: gfp_mask=0x24280ca, order=0, oom_score_adj=0
[123456.789] [ pid ]   uid  tgid total_vm      rss nr_ptes swapents oom_score_adj name
[123456.789] [ 1234]     0  1234   456789   234567    123       0             0 java
[123456.789] Out of memory: Killed process 1234 (java) total-vm:456789kB, anon-rss:234567kB, file-rss:0kB, shmem-rss:0kB

3.2 怎么避免被OOM Killer误杀

我个人的做法是:

  • 给关键进程设置oom_score_adj,降低被选中的概率
  • 用cgroup做内存限制,防止单个进程吃光所有内存
  • 配置swap空间,给系统留点缓冲
# 降低MySQL的oom_score,让它不容易被杀
echo -500 > /proc/$(pidof mysqld)/oom_score_adj

# 用cgroup限制Java进程最大内存
cgcreate -g memory:/java_app
echo 2G > /sys/fs/cgroup/memory/java_app/memory.limit_in_bytes
cgclassify -g memory:/java_app $(pidof java)

四、实战案例:一次Java进程被OOM Kill的完整复盘

我记得有一次,线上一个Java服务半夜挂了。监控显示内存从4GB慢慢涨到8GB,然后OOM了。

排查步骤:

  1. 先看dmesg,确认是OOM Killer干的
  2. 用jmap做堆转储(当时是低峰期,风险可控)
  3. 用MAT分析,发现一个ConcurrentHashMap占了6GB
  4. 查代码,发现是缓存没设过期时间,数据越积越多

解决方案很简单:给缓存加上expireAfterWrite策略,同时限制最大条目数。改完后,内存稳定在2GB左右,再也没OOM过。

避坑指南: 我曾经犯过一个错——以为OOM Killer只杀内存占用最大的进程。其实它算的是oom_score,跟进程的优先级、运行时间都有关系。所以别指望「我的进程内存不大就安全」。

五、知识体系梳理

下面这张图是我自己总结的CPU和内存故障排查流程,你可以保存下来当参考。

Linux系统故障排查流程 系统故障报警 CPU飙升100% 内存泄漏/OOM top 定位高CPU进程 strace 看系统调用 perf 生成火焰图 free -m 看内存趋势 dmesg 查OOM日志 jmap/valgrind定位泄漏 定位根因 & 修复

嗯,这张图基本把排查思路串起来了。从左边的CPU分支到右边的内存分支,最终都指向同一个目标——找到根因,彻底修复。

好了,这一章的内容就到这。CPU和内存的问题,说白了就是「谁在吃资源」和「谁吃了不还」这两个问题。掌握了排查工具和思路,下次再遇到报警,你就能从容应对了。

可可精品资料库