第四章:操作系统性能优化——CPU 与内存、磁盘的实战调优

大家好,我是你们的老朋友。今天咱们聊聊操作系统层面的性能优化。说实话,很多应用跑得慢,根子不在代码,而在操作系统。你想想看,CPU 调度不合理、内存泄漏、磁盘 I/O 卡顿,这些问题不解决,代码写得再漂亮也是白搭。

我个人习惯把操作系统优化分成三大块:CPU、内存、磁盘 I/O。咱们一个一个来拆解。

4.1 CPU 性能分析:top 与 perf 的实战用法

CPU 是服务器的“大脑”。它忙不忙、在忙什么,直接决定了你的服务响应快不快。

4.1.1 用 top 快速定位 CPU 瓶颈

top 命令是我日常用得最多的工具。没有之一。它就像服务器的“任务管理器”,一眼就能看出谁在吃 CPU。

# 直接运行 top
top

# 按 CPU 使用率排序(默认就是)
# 按 '1' 键查看每个 CPU 核心的负载
# 按 'P' 键按 CPU 使用率排序
# 按 'M' 键按内存使用率排序

这里有几个关键指标你要盯紧了:

  • us(user):用户态 CPU 时间。如果这个值高,说明你的应用在干活。
  • sy(system):内核态 CPU 时间。如果这个值高,说明系统调用频繁,可能有锁竞争或上下文切换过多。
  • wa(iowait):等待 I/O 完成的时间。这个值高,说明磁盘或网络是瓶颈。
  • id(idle):空闲时间。如果 id 很低,说明 CPU 已经满载了。

重要判断逻辑:

  • us 高 + id 低 → 应用层瓶颈,需要优化代码或增加 CPU 资源。
  • sy 高 + us 不高 → 系统调用或锁问题,检查上下文切换。
  • wa 高 → I/O 瓶颈,往下看磁盘优化部分。

我在项目中遇到过一个问题:某次线上服务响应突然变慢,top 一看 sy 飙到了 40%。我第一反应是“锁竞争”。用 perf 一查,果然是一个日志库在频繁加锁。嗯,换成了无锁队列,sy 直接降到了 5%。

4.1.2 用 perf 深挖 CPU 热点

top 只能告诉你哪个进程在吃 CPU,但 perf 能告诉你这个进程的哪一行代码在吃 CPU。这才是真正的“杀手锏”。

# 采样 10 秒,记录 CPU 事件
perf record -a -g -- sleep 10

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

# 用 FlameGraph 工具生成 SVG 火焰图
./stackcollapse-perf.pl out.perf > out.folded
./flamegraph.pl out.folded > cpu_flamegraph.svg

火焰图怎么看?很简单:

  • 横轴:采样次数,越宽说明该函数占用 CPU 越多。
  • 纵轴:调用栈,从下到上是调用关系。
  • 颜色:随机分配,没有特殊含义。

我曾经用 perf 抓到一个“隐藏很深”的性能问题。一个 Java 服务 CPU 居高不下,top 显示是 GC 线程在忙。但 perf 火焰图告诉我,GC 线程里 80% 的时间都在执行 System.currentTimeMillis()。为什么?因为某个日志框架每秒调用了几万次时间戳获取。改成缓存时间戳后,CPU 使用率直接腰斩。

我的小技巧:perf 采样时间不要太短,建议 10-30 秒。太短了采样点不够,看不准热点。太长了你可能等不及。10 秒是个不错的折中。

4.2 内存优化:泄漏排查与 OOM Killer 应对

内存问题比 CPU 问题更隐蔽。CPU 高了你能感觉到慢,内存泄漏了可能跑几天才出问题。我见过太多“跑着跑着就挂了”的案例,最后查出来都是内存泄漏。

4.2.1 内存泄漏排查三板斧

第一板斧:看趋势。用 free -m 或 top 观察内存使用量是否持续增长。如果重启后正常,跑几天又涨上去了,基本就是泄漏。

# 每 5 秒输出一次内存使用情况
watch -n 5 'free -m'

# 查看进程的详细内存映射
pmap -x <PID>

第二板斧:抓堆转储。对于 Java 或 Go 应用,堆转储是必杀技。

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

# 然后用 MAT 或 VisualVM 分析

第三板斧:跟踪内存分配。对于 C/C++ 应用,用 valgrind 或 AddressSanitizer。

# 用 valgrind 检测内存泄漏
valgrind --leak-check=full ./your_program

我记得有一次,一个 C++ 服务每 24 小时内存涨 200MB。用 valgrind 一查,发现是一个第三方库在每次请求时 new 了一个对象,但忘记 delete 了。修复后内存曲线变成了一条直线。

4.2.2 OOM Killer:别让它找上你

OOM Killer 是 Linux 内核的“最后手段”。当系统内存耗尽时,内核会杀掉一个进程来释放内存。被选中的进程通常是“得分最高”的那个——也就是占用内存最多、运行时间最短、优先级最低的进程。

怎么避免被 OOM Killer 盯上?

  • 设置 oom_score_adj:给关键进程设置较低的 oom 分数。
  • 限制内存使用:用 cgroups 或 ulimit 限制进程内存上限。
  • 开启 swap:虽然 swap 慢,但至少能避免瞬间 OOM。
# 查看进程的 OOM 分数
cat /proc/<PID>/oom_score

# 降低关键进程的 OOM 优先级(值越小越不容易被杀)
echo -1000 > /proc/<PID>/oom_score_adj

警告:千万不要把 oom_score_adj 设成 -1000 后就不管内存了。OOM Killer 是最后一道防线,不是让你依赖的。我曾经见过有人把所有进程都设成 -1000,结果系统直接死锁了——因为没有进程可以被杀,内核只能 panic。

4.3 磁盘 I/O 优化:iostat 与 iotop 实战

磁盘 I/O 是性能优化的“重灾区”。CPU 和内存再快,磁盘一卡,整个系统都得等。

4.3.1 用 iostat 看磁盘健康度

iostat 是我排查 I/O 问题的第一站。它告诉你磁盘在忙什么、忙到什么程度。

# 每 2 秒输出一次,显示扩展统计
iostat -x 2

# 只看某个磁盘
iostat -x sda 2

关键指标解读:

指标 含义 健康值
%util 磁盘忙绿百分比 < 70% 正常,> 90% 危险
await I/O 请求平均等待时间(ms) < 10ms 优秀,> 50ms 慢
svctm I/O 请求平均服务时间(ms) 接近 await 说明没有排队
r/s, w/s 每秒读写次数 取决于磁盘类型

我遇到过最典型的情况:%util 100% 但 r/s 很低。这说明磁盘在“假忙”——可能是磁盘硬件故障或驱动问题。换了一块硬盘后,%util 直接降到 20%。

4.3.2 用 iotop 抓出“吃 I/O”的进程

iostat 告诉你磁盘有问题,但 iotop 告诉你“谁”在搞鬼。

# 安装 iotop(通常需要 root)
apt-get install iotop

# 实时查看进程 I/O
iotop -oP

# -o:只显示有 I/O 的进程
# -P:只显示进程,不显示线程

实战中,我经常看到 flush 或 pdflush 进程占满 I/O。这通常意味着内存中的脏页太多,内核在拼命刷盘。解决办法:调整脏页比例。

# 查看当前脏页参数
sysctl vm.dirty_ratio
sysctl vm.dirty_background_ratio

# 临时调低脏页比例(生产环境要谨慎)
sysctl -w vm.dirty_ratio=10
sysctl -w vm.dirty_background_ratio=5

我的经验:对于写密集型的应用(比如日志、数据库),建议把 dirty_ratio 调低到 10-20%,dirty_background_ratio 调到 5-10%。这样能避免突然的大批量刷盘导致 I/O 尖刺。但别调太低,否则频繁刷盘反而增加 I/O 次数。

4.4 本章知识体系图

下面这张图总结了本章的核心逻辑。你可以把它当作一个“排查路线图”。

操作系统性能优化知识体系 CPU 性能分析 内存优化 磁盘 I/O 优化 top:查看进程 CPU 使用率 perf:采样 CPU 热点函数 火焰图:可视化调用栈 free/pmap:查看内存趋势 jmap/valgrind:堆转储分析 OOM Killer:oom_score_adj iostat -x:磁盘健康度 iotop:定位 I/O 进程 脏页调优:dirty_ratio 核心排查思路 1. 先看 CPU(top)→ 再看内存(free)→ 最后看磁盘(iostat) 2. 哪个指标异常,就用对应的工具深挖 3. 不要只看平均值,要看峰值和趋势

好了,这一章的内容就到这里。CPU、内存、磁盘这三板斧,你只要练熟了,大部分操作系统层面的性能问题都能快速定位。记住,工具只是手段,理解背后的原理才是关键。下次遇到性能问题,别急着加机器,先看看是不是操作系统层面有“坑”。

一句话总结:CPU 看 top + perf,内存看趋势 + 堆转储,磁盘看 iostat + iotop。这三套组合拳打出去,90% 的操作系统性能问题都能现出原形。

想要更多资料,联系可可