你的服务不是 CPU 不够快,是缺页中断在报警

凌晨两点线上服务变慢,CPU 不高内存不满但就是卡?这不是资源问题,是虚拟内存在报警。每个开发者都该懂的诊断技能。

凌晨两点,线上服务突然变慢。

你登上服务器,top 一看:CPU 占用 30%,内存也没满,磁盘 IO 正常。但请求就是卡,P99 从 50ms 跳到 800ms。

第一反应:加机器?

先别急。系统给你的信号不是"资源不够",是"内存用错了"——缺页中断在报警,你听不懂而已。

虚拟内存不是操作系统课程里的理论。它是每个写代码的人都应该能用来诊断问题的工具。

但 99% 的开发者都不理解虚拟内存。

你是那 1% 吗?


一、虚拟内存到底是什么

虚拟内存,就是让程序以为自己有很多内存,实际上用的是磁盘空间来凑。

你的机器只有 8GB RAM,但可以运行需要 12GB 内存的程序。多出来的 4GB 从哪来?从硬盘的交换空间(swap)借。

这不是魔法,是交换。

1
2
# 看看你的 swap 用了多少
free -h

输出:

              total        used        free      shared  buff/cache   available
Mem:           15Gi       8.2Gi       2.1Gi       1.2Gi       5.7Gi       6.8Gi
Swap:         8.0Gi       1.5Gi       6.5Gi

看到 Swap used 1.5Gi 了吗?这意味着有 1.5GB 的数据被从 RAM 换出到磁盘了。

经验值: swap 使用率超过 30%,服务延迟大概率会抖。不是绝对,但值得警惕。


二、地址空间:每个进程都活在自己的世界里

每个进程启动时,操作系统都会给它一个幻觉:整个内存都是你的。

你的程序访问地址 0x00000000,不会知道这个地址实际对应物理内存的哪个位置。操作系统用一张页表帮你做映射。

内存被切成固定大小的块,叫页(Page),通常是 4KB。虚拟页映射到物理内存的帧(Frame)

概念说明
虚拟内存的块(4KB)
物理内存的块

为什么需要这层抽象?

想象没有虚拟内存的世界:

  • 每个程序必须知道自己能用哪些物理地址
  • 程序 A 可能意外覆盖程序 B 的内存
  • 你无法运行比物理 RAM 更大的程序

虚拟内存解决了所有这些问题。

页表映射图


三、缺页中断:系统卡顿的真正原因

按需分页

程序需要的数据不在 RAM 里时,会发生什么?

  1. CPU 发现请求的页面不在内存中
  2. 触发缺页中断(page fault)
  3. 操作系统接管,从磁盘的交换空间读取页面
  4. 页面加载到 RAM 的空闲帧
  5. 更新页表映射
  6. 程序继续执行

这个过程需要访问磁盘——比访问 RAM 慢 1000 倍到 10 万倍。

存储介质访问延迟相对速度
RAM~100 ns1x
SSD~100 μs1000x 慢
HDD~10 ms100,000x 慢

这就是为什么 swap 一高,系统就卡。

抖动(Thrashing)

当缺页中断变得极其频繁时,系统会进入抖动状态:

  • 系统花费大部分时间在页面交换上
  • 实际工作几乎无法进行
  • 硬盘灯常亮,但什么都做不了

抖动的症状:

  • 系统响应极慢
  • 磁盘 IO 等待高(iowait)
  • 应用程序卡死

怎么判断是不是抖动?

1
2
# Linux 实时查看缺页中断
vmstat 1

输出:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  1 1572864  12345  23456 345678  123  456   789  1011 1213 1415 16 18 66  0  0

关注这两列:

  • si(swap in):从磁盘读入内存的页面数
  • so(swap out):从内存换出到磁盘的页面数

如果 si 和 so 持续大于 0,说明系统在抖动。 这时候加 CPU 没用,加内存才有用。

缺页中断流程图


四、TLB:地址转换的缓存

TLB 是什么

每次内存访问都需要查页表——这需要额外访问内存,会慢 2 倍。

TLB(Translation Lookaside Buffer) 缓存了最近的地址转换。命中时可以直接使用,无需查页表。

情况说明性能影响
TLB 命中地址转换在 TLB 中找到快速,直接访问
TLB 缺失需要查页表慢,需要额外内存访问

TLB 缺失的表现:

  • CPU 周期消耗在地址转换上
  • 内存密集型应用性能下降明显

怎么查看 TLB 缺失?

1
2
# 使用 perf 查看 TLB 缺失
perf stat -e dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses ./your-program

输出:

Performance counter stats for './your-program':
    1,234,567,890      dTLB-loads
       12,345,678      dTLB-load-misses    #    1.00% of all dTLB loads

经验值: TLB 缺失率超过 5%,内存访问模式可能有问题。可能是随机访问太多,或者数据结构太大。


五、虚拟内存的优势

1. 运行大型程序

程序可以使用的内存超过物理可用量。

例子: 你的机器只有 8GB RAM,但可以运行需要 12GB 内存的视频编辑软件。

2. 多任务支持

多个应用程序可以同时运行,无需担心 RAM 不足。

典型场景:

  • 带 50 个标签页的浏览器
  • VS Code 编辑文档
  • Docker 容器
  • 微信、Slack 等通讯工具

总需求可能超过 20GB,但你的机器只有 16GB RAM。虚拟内存确保不活跃的应用被移到磁盘,让活跃应用流畅运行。

3. 内存隔离

每个进程有自己的内存空间,防止一个程序干扰另一个程序。

安全意义: 恶意程序无法直接访问其他进程的内存。


六、虚拟内存的代价

1. 性能开销

从磁盘访问数据比访问 RAM 慢得多。

这就是为什么抖动时系统会卡死。

2. 增加 SSD 磨损

过度交换会减少 SSD 寿命。

原因: SSD 使用闪存芯片,有写入次数限制。频繁交换会加速磨损。

建议:

  • 如果有足够 RAM,可以减少 swap 使用
  • 使用 HDD 做 swap(如果有的话)
  • 监控 swap 使用率

3. 调试复杂度

虚拟内存让问题更隐蔽。

典型场景: 你的程序内存泄漏,但因为虚拟内存的存在,不会立即崩溃。等 swap 用满,直接 OOM killer 杀掉进程。


七、实操:诊断虚拟内存问题

Linux

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 1. 查看内存和 swap 使用情况
free -h

# 2. 查看详细内存统计
cat /proc/meminfo

# 3. 实时查看虚拟内存统计
vmstat 1

# 4. 查看每个进程的 swap 使用
for file in /proc/*/status ; do 
    awk '/VmSwap|Name/{printf $2 " " $3}END{ print ""}' $file
done | sort -k 2 -n -r | head -10

关键指标解读:

指标说明警戒线
available可用内存(包括可回收缓存)低于 20% 需要注意
Swap used已使用的交换空间超过 30% 大概率卡顿
si/soswap 进出速率持续大于 0 说明抖动
iowaitCPU 等待 IO 的时间超过 20% 说明 IO 瓶颈

macOS

1
2
3
4
5
# 查看虚拟内存统计
vm_stat

# 使用活动监视器(图形界面)
open -a "Activity Monitor"

vm_stat 输出解读:

Pages free:                               123456
Pages active:                             234567
Pages inactive:                           345678
Pages speculative:                         12345
Pages throttled:                               0
Pages wired down:                         456789
"Translation faults":                   12345678

关键指标:

  • Pages free:空闲页面
  • Pages active:活跃页面(正在使用)
  • Pages inactive:非活跃页面(可能被换出)
  • Translation faults:缺页中断次数(累计)

Windows

1
2
3
4
5
6
# 使用任务管理器(图形界面)
taskmgr

# PowerShell 查看内存信息
Get-CimInstance Win32_OperatingSystem | 
    Select-Object TotalVisibleMemorySize,FreePhysicalMemory

八、诊断清单:下次系统卡顿时按这个来

第一步:确认是不是内存问题

1
2
free -h
vmstat 1
  • Swap 使用率超过 30%?→ 内存不足
  • si/so 持续大于 0?→ 正在抖动

第二步:找出哪个进程在吃内存

1
2
3
4
5
6
7
# 按内存排序
ps aux --sort=-%mem | head -10

# 查看每个进程的 swap 使用
for file in /proc/*/status ; do 
    awk '/VmSwap|Name/{printf $2 " " $3}END{ print ""}' $file
done | sort -k 2 -n -r | head -10

第三步:决定怎么处理

情况处理方式
单个进程内存泄漏重启该进程,修复代码
所有进程都吃内存加内存或减少并发
偶发抖动优化内存访问模式
持续抖动必须加内存,别犹豫

第四步:验证

1
2
3
# 处理后再次检查
free -h
vmstat 1
  • Swap 使用率下降到 30% 以下?
  • si/so 回到 0?
  • 系统响应恢复正常?

九、最后

回到开篇的判断:

你的服务不是 CPU 不够快,是缺页中断在报警。

虚拟内存不是黑魔法。它是每个开发者都应该理解的核心抽象。

理解虚拟内存,你才能:

  • 诊断抖动问题
  • 理解 OOM killer 为什么会杀死你的进程
  • 合理配置 swap 空间
  • 写出更高效的代码

下次系统卡顿时,别急着加机器。

先问自己:

  • 是 CPU 瓶颈还是内存瓶颈?
  • swap 使用率是多少?
  • 缺页中断频率如何?

从"系统好卡"到"缺页中断频率过高,需要优化内存使用",这才是专业开发者该有的诊断能力。

记住这句话: 系统卡顿不是"电脑老了",是虚拟内存在报警——你得听得懂。