Linux 内存页错误 (Page Fault) 故障排查实战

一、问题背景

内存页错误(Page Fault)是 Linux 系统中常见的内存访问异常,当进程访问的虚拟内存页不在物理内存中时触发。虽然大多数 Page Fault 是正常的(软缺页),但频繁的硬缺页会导致系统性能严重下降,甚至引发 OOM Killer 或应用崩溃。

本文通过实际案例,详细介绍 Page Fault 的排查思路、诊断工具和解决方案。

二、Page Fault 基础概念

2.1 Page Fault 类型

类型 英文 说明 性能影响
软缺页 Minor Page Fault 页已在内存中,只需更新页表 轻微
硬缺页 Major Page Fault 页不在内存中,需从磁盘加载 严重
无效缺页 Invalid Page Fault 访问非法地址,导致段错误 崩溃

2.2 常见触发场景

  • 应用程序首次访问新分配的内存
  • 内存交换(Swap)频繁
  • 共享库加载
  • 内存映射文件访问
  • 内存泄漏导致频繁换页

三、故障现象识别

3.1 系统层面症状

1
2
# 查看系统整体缺页统计
vmstat 1 5

输出示例:

1
2
3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 204800 51200 32000 512000 150 300 200 100 1500 3000 25 15 55 5 0

关键字段:

  • si (swap in): 每秒从磁盘换入的页数,持续>100 需关注
  • so (swap out): 每秒换出到磁盘的页数,持续>100 需关注

3.2 进程层面症状

1
2
# 查看进程的缺页统计
pidstat -p <PID> -w 1 5

输出示例:

1
2
11:00:00      PID   cswch/s nvcswch/s  minflt/s  majflt/s   Command
11:00:01 1234 150.00 12.00 500.00 50.00 java

关键字段:

  • minflt/s: 每秒软缺页次数
  • majflt/s: 每秒硬缺页次数,持续>10 需重点关注

3.3 使用 perf 深入分析

1
2
3
4
5
# 记录缺页事件
perf record -e page-faults -p <PID> sleep 30

# 分析热点
perf report --stdio

四、诊断工具集

4.1 /proc 文件系统

1
2
3
4
5
# 查看进程内存统计
cat /proc/<PID>/status | grep -E "VmSize|VmRSS|VmSwap|MinFlt|MajFlt"

# 查看系统级缺页统计
cat /proc/vmstat | grep -E "pgfault|pgmajfault"

4.2 sar 历史数据分析

1
2
3
4
5
# 查看历史缺页数据
sar -B 1 5

# 查看特定时间段的缺页情况
sar -B -f /var/log/sa/sa25

4.3 smem 内存分析

1
2
3
4
5
6
7
8
9
# 安装 smem
apt install smem # Ubuntu/Debian
yum install smem # CentOS/RHEL

# 查看进程内存详细统计
smem -p <PID>

# 按进程统计内存
smem -tk

4.4 pmap 内存映射分析

1
2
3
4
5
# 查看进程内存映射
pmap -x <PID>

# 详细模式
pmap -XX <PID>

五、实战案例

案例一:Java 应用频繁硬缺页

现象

某 Java 应用响应时间从 50ms 突增至 500ms,系统负载正常但 swap 使用率持续上升。

排查过程

1
2
# 1. 确认缺页情况
pidstat -p 1234 -w 1 10

输出显示 majflt/s 持续在 80-120 之间。

1
2
# 2. 检查内存使用
cat /proc/1234/status | grep -E "VmSize|VmRSS|VmSwap"

结果:

1
2
3
VmSize:  8388608 kB    # 虚拟内存 8GB
VmRSS: 2097152 kB # 物理内存 2GB
VmSwap: 524288 kB # 交换内存 512MB
1
2
# 3. 分析内存映射
pmap -x 1234 | tail -20

发现大量匿名内存映射被换出。

根因分析

  1. JVM 堆内存设置过大(-Xmx6g),超出物理内存承载能力
  2. 系统其他进程占用大量内存,触发内存压力
  3. 频繁 GC 导致内存页频繁换入换出

解决方案

1
2
3
4
5
6
7
# 方案 1: 调整 JVM 堆内存
JAVA_OPTS="-Xms2g -Xmx2g -XX:+UseG1GC"

# 方案 2: 增加物理内存或减少并发进程

# 方案 3: 调整 swappiness 降低换页倾向
sysctl vm.swappiness=10

案例二:数据库服务内存页错误

现象

MySQL 服务在高峰期间出现大量 majflt,查询延迟增加 3 倍。

排查过程

1
2
3
4
5
6
7
8
# 1. 实时监控缺页
watch -n1 'grep majflt /proc/vmstat'

# 2. 查看 MySQL 进程内存
pidstat -p $(pgrep mysqld) -w 1

# 3. 检查 buffer pool 配置
mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool%';"

根因分析

  1. innodb_buffer_pool_size 设置为 12GB,但系统总内存仅 16GB
  2. 操作系统和其他进程需要至少 4GB 内存
  3. 导致 MySQL 缓冲池频繁被换出

解决方案

1
2
3
4
# my.cnf 调整
[mysqld]
innodb_buffer_pool_size = 8G
innodb_buffer_pool_instances = 8
1
2
3
# 调整系统内存分配倾向
echo 'vm.swappiness=1' >> /etc/sysctl.conf
sysctl -p

案例三:容器应用 Page Fault 爆发

现象

Kubernetes 集群中某 Pod 频繁重启,节点 dmesg 出现大量 page fault 相关日志。

排查过程

1
2
3
4
5
6
7
8
# 1. 查看节点缺页统计
cat /proc/vmstat | grep pg

# 2. 查看容器内存限制
kubectl describe pod <pod-name> | grep -A5 "Limits"

# 3. 检查容器内进程
kubectl exec <pod-name> -- cat /proc/1/status | grep -E "Vm|flt"

根因分析

  1. 容器内存 limit 设置过小(512MB)
  2. 应用实际内存需求约 800MB
  3. 触发 OOM Killer 前频繁换页

解决方案

1
2
3
4
5
6
# deployment.yaml 调整
resources:
requests:
memory: "512Mi"
limits:
memory: "1Gi" # 从 512Mi 提升至 1Gi

六、预防与优化

6.1 内存配置最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 合理设置 swappiness(默认 60)
# 服务器建议:10,数据库建议:1
echo 'vm.swappiness=10' >> /etc/sysctl.conf

# 2. 调整 vfs_cache_pressure(默认 100)
# 增加目录项和 inode 缓存保留倾向
echo 'vm.vfs_cache_pressure=50' >> /etc/sysctl.conf

# 3. 设置最小空闲内存
echo 'vm.min_free_kbytes=65536' >> /etc/sysctl.conf

sysctl -p

6.2 应用层优化

1
2
3
4
5
6
7
8
9
# 1. 预分配内存(减少首次访问缺页)
# C 语言示例:使用 mmap 预分配
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
// 预触摸内存页
memset(addr, 0, size);

# 2. 使用 hugepages(减少页表项)
echo 512 > /proc/sys/vm/nr_hugepages

6.3 监控告警配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Prometheus 告警规则
groups:
- name: memory_page_fault
rules:
- alert: HighMajorPageFaults
expr: rate(node_vmstat_pgmajfault[5m]) > 100
for: 5m
labels:
severity: warning
annotations:
summary: "高频率硬缺页"
description: "节点 {{ $labels.instance }} 硬缺页率 {{ $value }}/s"

- alert: ProcessMajorPageFaults
expr: rate(process_majflt_total[5m]) > 50
for: 5m
labels:
severity: warning
annotations:
summary: "进程硬缺页异常"
description: "进程 {{ $labels.process }} 硬缺页率 {{ $value }}/s"

七、排查流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
发现性能下降

检查 vmstat si/so 指标

si/so 持续高?──否──→ 排查其他原因
↓是
pidstat 定位高缺页进程

分析进程内存使用 (pmap/smem)

检查应用内存配置

调整配置/优化代码/扩容

验证效果并持续监控

八、常用命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 系统级缺页监控
vmstat 1
sar -B 1 5

# 进程级缺页监控
pidstat -p <PID> -w 1

# 内存状态查看
cat /proc/meminfo
cat /proc/<PID>/status

# 内存映射分析
pmap -x <PID>
smem -p <PID>

# 性能分析
perf record -e page-faults -p <PID>
perf report

# 历史数据
sar -B -f /var/log/sa/saXX

九、总结

Page Fault 故障排查的关键点:

  1. 区分软硬缺页:硬缺页(majflt)才是性能杀手
  2. 定位问题进程:使用 pidstat 快速定位
  3. 分析内存配置:检查应用和系统内存设置是否合理
  4. 监控换页行为:关注 si/so 指标变化趋势
  5. 综合优化:结合应用配置、系统参数、硬件资源综合调整

通过本文的方法论和工具集,可以快速定位并解决大多数 Page Fault 相关的性能问题。


参考文档:

  • Linux Kernel Documentation: Memory Management
  • perf-tools GitHub Repository
  • Ubuntu Performance Tuning Guide