Linux内存管理全解析:从原理到实践,让你的服务器不再“内存不足”

Linux内存管理全解析:从原理到实践,让你的服务器不再“内存不足”

在日常运维中,你是否也经常看到服务器内存使用率达到90%以上,然后紧急扩容?实际上,Linux的内存管理机制远比我们想象的要复杂和智能。今天,我们就来深入探讨Linux内存管理的核心机制,以及在生产环境中该如何正确理解和优化内存使用。

举例
企业的监控系统频繁发出内存告警,显示多台服务器内存使用率超过95%。运维团队紧急排查,却发现应用响应速度正常,系统并未发生OOM(Out Of Memory)。

原因:Linux内核的Page Cache机制在“作祟”。当内存空闲时,内核会将部分内存用于缓存 磁盘数据(Page Cache),以加速后续的磁盘读取操作。这部分内存在应用程序需要时会立即释放,因此监控中的“高内存使用率”往往是一种假象。

# 查看Page Cache大小
$ free -h
total used free shared buff/cache available
Mem: 62G 8.1G 1.2G 1.5G 53G 52G
Swap: 0B 0B 0B

这里的buff/cache就是Page Cache,它占用了53G内存,这部分内存是“可回收”的。

Linux内存管理核心架构
内核内存分配机制
Linux 采用页式内存管理,默认页大小为 4KB。内核通过 Buddy System 管理物理页,使用 Slab Allocator 处理小对象分配(如 kmalloc 请求)。

生产环境关键认知:

匿名页(Anonymous Pages):进程堆栈、数据段,无文件 backing,swap 的主要对象
文件页(File Pages):Page Cache,用于缓冲磁盘数据,可被内核随时回收
内核内存(Kernel Memory):Slab、页表、per-cpu 变量,对容器环境尤为关键
查看实时构成:
cat /sys/fs/cgroup/memory.stat
# 输出示例:
# anon 104857600 # 100MB 匿名内存
# file 52428800 # 50MB 文件缓存
# kernel_stack 8192 # 内核栈占用

内存回收与 OOM 机制
当可用内存低于水位线(watermark)时,kswapd 后台线程启动回收;若压力持续,触发直接回收(Direct Reclaim),导致进程阻塞——这是生产环境延迟尖峰(latency spike)的主要元凶。

OOM Killer 策略(/proc/sys/vm/oom_kill_allocating_task):

0:扫描所有进程,选择 oom_score 最高者(默认)
1:直接杀死触发 OOM 的进程,减少扫描开销
我们可以通过调整权重来保护关键进程:
# 保护MySQL进程
echo -1000 > /proc/$(pgrep mysqld)/oom_score_adj

四大核心机制详解
Page Cache
Page Cache是Linux内存管理中最智能的部分之一,它通过缓存磁盘数据来提升系统性能。这也是为什么我们经常看到服务器内存使用率很高,但系统依然正常运行的原因。

Swap
Swap空间是磁盘上的一块区域,当物理内存不足时,内核会将不常用的内存页换出到Swap。然而,在现代生产环境中:

对于数据库服务器:禁用Swap是常见做法,因为磁盘I/O速度远慢于内存
对于应用服务器:保留少量Swap(如4GB)作为缓冲,避免直接被OOM Killer杀死进程

透明大页(THP)

# 查看THP状态
cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never # 输出 [always] 表示全局开启

THP可以减少TLB缺失,提升大内存应用的性能,但可能导致内存碎片和延迟波动。生产环境建议

# 调整为madvise,让应用自己决定
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled

推荐关闭 THP 的场景
延迟敏感型数据库/大数据服务

典型应用:Oracle、MySQL、PostgreSQL 、Redis、Doris、Elasticsearch 等。
原因:

THP 的后台守护进程 khugepaged 会频繁扫描并合并小页,导致不可预测的 CPU 峰值和内存锁竞争,引发查询延迟抖动(微秒至毫秒级)。
内存碎片化严重时,THP 可能触发激进的直接内存回收(Direct Compaction),造成性能骤降甚至服务超时。
可开启 THP 的场景
通用计算负载

典型场景:Web 服务器、文件服务、开发环境等内存访问模式较均匀的应用。
优势:

自动提升 TLB 命中率,减少页表项开销,无需应用修改即可获得性能收益。
永久关闭(推荐)
在 /etc/default/grub 的 GRUB_CMDLINE_LINUX 追加 transparent_hugepage=never,执行 update-grub && reboot。
验证效果

grep AnonHugePages /proc/<pid>/smaps # 若输出全 0 表示 THP 已禁用

OOM Killer:残酷的生存游戏

当系统真正内存不足时,OOM Killer会根据每个进程的oom_score选择“牺牲者”。合理配置进程的oom_score_adj可以保护关键服务。

# 保护MySQL进程
echo -1000 > /proc/$(pgrep mysqld)/oom_score_adj

Cgroups v2:云原生时代的资源管控标准
从 v1 到 v2 的范式转移
Cgroups v2 采用统一层级结构(Unified Hierarchy),解决了 v1 中多个子系统独立挂载的复杂性。目前 Ubuntu 22.04+、RHEL 9、Fedora 31+ 已默认启用。

内存控制模型
Cgroups v2 引入精细化的内存控制策略,类似水库的三级闸门:

memory.low(软保护)

最佳努力保护,内核优先回收其他 cgroup 内存
Kubernetes Guaranteed QoS Pod 的底层实现
memory.high(节流阀)

超过此值内核启动激进回收,进程进入节流状态
生产陷阱:应用在 OOM 前可能已因 memory.high 节流而性能暴跌,但监控往往只关注 OOM
memory.max(硬限制)

绝对上限,触发 OOM Killer
生产配置示例
配置仅供参考,实际情况是需要根据自己服务器配置情况进行修改。

# 创建生产级 cgroup
mkdir -p /sys/fs/cgroup/prod-api

# 启用内存控制器
echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control

# 设置三级闸门(单位:字节)
echo 8589934592 > /sys/fs/cgroup/prod-api/memory.low # 8GB 保护
echo 12884901888 > /sys/fs/cgroup/prod-api/memory.high # 12GB 开始节流
echo 17179869184 > /sys/fs/cgroup/prod-api/memory.max # 16GB 硬限制

# 迁移进程
echo <PID> > /sys/fs/cgroup/prod-api/cgroup.procs

# 监控实时压力
cat /sys/fs/cgroup/prod-api/memory.pressure

PSI 量化资源压力的利器

Pressure Stall Information(PSI)是 Linux 内核版本 4.20+ 引入的精确压力度量机制,提供墙钟时间占比而非简单的计数器。

PSI 核心指标解读

cat /sys/fs/cgroup/prod-api/memory.pressure # 如果提示没有这个文件,请看下面的开启方法
# some avg10=0.12 avg60=0.34 avg300=1.23 total=12345678
# full avg10=0.00 avg60=0.01 avg300=0.05 total=987654

  • some:至少一个任务因内存等待停滞的时间占比(>0 即表示存在节流)
  • full:所有任务同时停滞的时间占比(预示 OOM 风险)

生产告警阈值建议

  • some avg60 > 5%:黄色预警,存在内存压力
  • some avg60 > 20%:红色告警,严重节流
  • full avg10 > 1%:紧急状态, imminent OOM

启用 PSI

grubby --update-kernel=ALL --args="psi=1"
reboot

激活cgroup控制器‌

# 在根cgroup启用控制器
echo "+memory +io +cpu" | sudo tee /sys/fs/cgroup/cgroup.subtree_control

# 在目标cgroup启用控制器(如prod-api)
mkdir -p /sys/fs/cgroup/prod-api
echo "+memory +io +cpu" | sudo tee /sys/fs/cgroup/prod-api/cgroup.subtree_control

# 验证控制器状态
cat /sys/fs/cgroup/prod-api/cgroup.controllers # 应包含memory/io/cpu

再次查看即可

# 应正常输出压力数据
cat /sys/fs/cgroup/prod-api/memory.pressure
# 示例输出:avg10=0.00 avg60=0.00 avg300=0.00 total=0

与 Kubernetes 的集成

Kubernetes 依赖 kubelet 监控 PSI 实现主动驱逐(Eviction):

  • 在节点级内存压力时,优先驱逐 BestEffort/Burstable Pod
  • 避免触发系统级 OOM Killer 导致关键服务随机死亡

eBPF 内存监控的终极武器
内核级内存追踪
eBPF 允许在内核态安全执行自定义代码,实现亚微秒级的内存事件追踪,且几乎无性能损耗。

典型应用场景:

追踪 malloc/free 调用栈,定位内存泄漏
监控 Slab 分配器碎片化
统计 Page Cache 命中率
BPF Map 内存管控要点
从 Cgroups v2 开始,BPF Map 占用的内核内存被正确计入 cgroup 账户。这对使用 eBPF 的监控/安全工具(如 Cilium、Falco)至关重要:

命令安装
Ubuntu/Debian
apt install linux-tools-$(uname -r)
CentOS/RHEL/Alibaba Cloud Linux‌

yum -y install bpftool

# 查看 BPF Map 内存占用(需 root)
bpftool map -j | jq '[ .[] | .bytes_memlock ] | add / 1024 / 1024'
# 输出:总占用 MB 数

# 按进程聚合
bpftool map -j | jq 'group_by(.pids[0].comm) | map({comm: .[0].pids[0].comm, total_mb: (map(.bytes_memlock) | add / 1024 / 1024)}) | sort_by(.total_mb)'

生产建议:未使用的 Map 应设置 max_entries=1 并在加载时动态调整,避免空 Map 占用大量锁定内存(locked memory)。

生产环境最佳实践
监控:看对指标才是关键
不要只看used内存,更应该关注:

可用内存:available字段(包含可回收的Cache)
Swap使用率:即使有Swap,使用率也不应持续高于10%
页面交换:si(swap in)和so(swap out)次数
PSI指标:监控内存压力的真实情况

$ 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 0 0 1234567 891011 13141516 0 0 0 0 0 0 10 5 85 0 0

参数调优:因“应用”制宜

/etc/sysctl.conf中根据应用类型调整:

内存敏感型应用(如Redis):

# 降低swap使用倾向
vm.swappiness = 1
# 尽早触发内存回收
vm.vfs_cache_pressure = 200

文件服务器:

# 保留更多Cache
vm.vfs_cache_pressure = 50
# 允许更多脏页
vm.dirty_ratio = 40
vm.dirty_background_ratio = 10

容器化部署 checklist

强制使用 Cgroups v2

# 检查当前版本
stat -fc %T /sys/fs/cgroup/
# 应输出:cgroup2fs

合理设置 memory.high:

设置为 memory.limit 的 90%,提前触发节流而非 OOM
Java/Go 应用需结合 GC 调优,避免与内核回收冲突
启用 PSI 监控:

部署 node-exporter 1.3.0+ 采集 PSI 指标
Grafana 配置 rate(node_pressure_memory_waiting_seconds_total[1m]) 告警
Kubernetes 环境配置:
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
resources:
requests:
memory: "512Mi" # 调度依据
limits:
memory: "1Gi" # 超过此值会被OOM Kill

# 查看当前大页配置
cat /proc/sys/vm/nr_hugepages

# 动态分配 1024 个 2MB 大页
echo 1024 > /proc/sys/vm/nr_hugepages

# 在 cgroup v2 中限制 HugeTLB 使用
echo 1073741824 > /sys/fs/cgroup/prod-api/hugetlb.2MB.max

 

 

上一篇 Smartctl
下一篇 浪潮服务器使用深度远程启动管理器配置BMC DHCP管理地址的方法