一、概述
背景介绍
每隔一段时间,我都会收到这样的告警:"服务器内存使用率超过 90%,请及时处理"。然后一群人开始紧张兮兮地讨论要不要重启服务、要不要扩容。等我登上机器一看,buff/cache 占了一大半,实际业务内存根本没什么压力。
这个场景在生产环境中太常见了。很多运维同学,甚至一些开发同学,对 Linux 内存管理的理解还停留在 Windows 那套思维上——觉得内存占用高就是不好的,恨不得把所有缓存都清掉。这是一个非常大的误区。
Linux 的设计哲学是"内存不用白不用"。空闲的内存与其让它闲着,不如拿来做缓存,加速文件读写。当应用程序真正需要内存的时候,这些缓存是可以立即释放的。理解这一点,是正确做 Linux 内存管理的前提。
技术特点
Linux 内存管理有几个核心特点:
1. 延迟分配 (Lazy Allocation)
进程 malloc 申请内存的时候,内核并不会立即分配物理内存,而是先给一个虚拟地址空间。等进程真正访问这块内存的时候,触发缺页异常,内核才会分配物理页面。这就是为什么你经常看到进程的 VIRT 很大但 RSS 很小。
2. 内存回收机制
当物理内存紧张的时候,内核会通过多种方式回收内存:
3. Buffer 和 Cache 的可回收性
这是本文的重点。Buffer 和 Cache 本质上都是内核为了提升 I/O 性能而使用的内存,在内存紧张时可以被回收。但"可回收"不等于"应该主动清除"。
适用场景
本文的内容适用于以下场景:
环境要求
本文的命令和配置基于以下环境验证:
- CentOS 7.x / RHEL 7.x 及以上版本
- Ubuntu 18.04 / 20.04 / 22.04
- 内核版本 3.10 及以上(推荐 4.x 或 5.x)
不同发行版的内核参数位置和默认值可能略有差异,但核心原理是一致的。
二、详细步骤
2.1 free 命令的正确解读
先来看 free 命令的输出:
$ free -h
total used free shared buff/cache available
Mem: 31Gi 12Gi 2.1Gi 1.2Gi 17Gi 17Gi
Swap: 8.0Gi 512Mi 7.5Gi
很多人看到 free 只有 2.1G 就慌了,觉得内存快用完了。但实际上,真正需要关注的是 available 这一列。
各字段含义
关键理解:available = free + 可回收的 buff/cache
available 是内核综合评估后给出的一个值,它考虑了:
- Slab 中可回收的部分(主要是 dentry cache 和 inode cache)
从 Linux 3.14 内核开始,内核直接提供了 available 的计算,比之前的估算更准确。
一个常见的误区
老版本的 free 命令(CentOS 6 时代)输出是这样的:
total used free shared buffers cached
Mem: 32946904 31593688 1353216 0 381288 25674464
-/+ buffers/cache: 5537936 27408968
Swap: 8388604 524288 7864316
第二行 -/+ buffers/cache 才是真正可用的内存。很多老运维习惯看第一行的 free,就会得出"内存快用完了"的错误结论。
新版 free 命令的 available 字段解决了这个问题,但很多监控系统的告警规则还是用的 (total - free) / total 这种算法,导致误告警。
正确的内存使用率计算公式:
# 错误的计算方式(会触发大量误告警)
memory_used_percent = (total - free) / total * 100
# 正确的计算方式
memory_used_percent = (total - available) / total * 100
2.2 Buffer vs Cache 的本质区别
这是一个经典的面试题,也是很多人搞混的概念。
Buffer(缓冲区)
Buffer 是对块设备的缓冲。什么是块设备?就是那些以固定大小块为单位读写的设备,典型的就是硬盘。
Buffer 主要用于:
- 块设备的元数据缓存(superblock、inode 等)
在现代 Linux 中,Buffer 的占用通常很小,因为大部分磁盘 I/O 都走文件系统,会被 Cache 缓存。
你可以通过直接操作块设备来观察 Buffer 的变化:
# 查看当前 buffer 大小
$ grep -i buffer /proc/meminfo
Buffers: 381288 kB
# 直接读取块设备(需要 root 权限)
$ dd if=/dev/sda of=/dev/null bs=1M count=100
# 再次查看,buffer 会增加
$ grep -i buffer /proc/meminfo
Buffers: 483512 kB
Cache(缓存)
Cache 是对文件系统的缓存,也叫 Page Cache。这是内存中占大头的部分。
Cache 的作用:
# 读取一个大文件,观察 cache 变化
$ cat /var/log/bigfile.log > /dev/null
# 查看 cached 大小
$ grep -i "^cached" /proc/meminfo
Cached: 25674464 kB
内核中的统一管理
从 Linux 2.4 内核开始,Buffer 和 Cache 的底层实现已经统一到 Page Cache 框架下。Buffer 实际上也是用 Page Cache 的页面来存储的。
所以在 free 命令中,它们被合并显示为 buff/cache。要分别查看,需要去 /proc/meminfo:
$ grep -E "^(Buffers|Cached|SwapCached)" /proc/meminfo
Buffers: 381288 kB
Cached: 25674464 kB
SwapCached: 12340 kB
2.3 /proc/meminfo 详解
/proc/meminfo 是 Linux 内存信息的权威来源。free 命令本质上也是读取这个文件。
$ cat /proc/meminfo
MemTotal: 32946904 kB
MemFree: 1353216 kB
MemAvailable: 27408968 kB
Buffers: 381288 kB
Cached: 25674464 kB
SwapCached: 12340 kB
Active: 18234560 kB
Inactive: 11234567 kB
Active(anon): 8234560 kB
Inactive(anon): 3234567 kB
Active(file): 10000000 kB
Inactive(file): 8000000 kB
Unevictable: 12345 kB
Mlocked: 12345 kB
SwapTotal: 8388604 kB
SwapFree: 7864316 kB
Dirty: 12345 kB
Writeback: 0 kB
AnonPages: 5537936 kB
Mapped: 987654 kB
Shmem: 1234567 kB
KReclaimable: 1234567 kB
Slab: 2345678 kB
SReclaimable: 1234567 kB
SUnreclaim: 1111111 kB
KernelStack: 12345 kB
PageTables: 123456 kB
...
核心字段解读
内存总量和可用
MemAvailable:应用程序可用的内存(内核估算值)
缓存相关
Cached:Page Cache(不含 SwapCached)SwapCached:曾经被换出后又换入的页面,仍在 swap 中有副本
活跃/不活跃页面
Inactive:一段时间未被访问的页面,可能被回收Active(anon):活跃的匿名页面(进程堆、栈等)Active(file):活跃的文件页面(Page Cache)
匿名页面和映射
AnonPages:匿名页面总量(不与文件对应的页面)
Slab 缓存
SReclaimable:可回收的 Slab(主要是 dentry cache、inode cache)
脏页和写回
内存去向分析
拿到一台机器,想知道内存都去哪了,可以这样分析:
#!/bin/bash
# memory_analysis.sh - 内存去向分析脚本
echo"=== 内存去向分析 ==="
awk '
/^MemTotal:/ { total = $2 }
/^MemFree:/ { free = $2 }
/^MemAvailable:/ { available = $2 }
/^Buffers:/ { buffers = $2 }
/^Cached:/ { cached = $2 }
/^SwapCached:/ { swapcached = $2 }
/^AnonPages:/ { anon = $2 }
/^Shmem:/ { shmem = $2 }
/^Slab:/ { slab = $2 }
/^SReclaimable:/ { sreclaimable = $2 }
/^SUnreclaim:/ { sunreclaim = $2 }
/^KernelStack:/ { kernelstack = $2 }
/^PageTables:/ { pagetables = $2 }
END {
printf "物理内存总量: %8d MB\n", total/1024
printf "完全空闲: %8d MB\n", free/1024
printf "可用内存: %8d MB\n", available/1024
printf "\n=== 内存去向 ===\n"
printf "进程匿名页面: %8d MB (堆、栈、匿名mmap)\n", anon/1024
printf "Page Cache: %8d MB (文件缓存)\n", (cached-shmem)/1024
printf "共享内存/tmpfs: %8d MB\n", shmem/1024
printf "Buffers: %8d MB (块设备缓冲)\n", buffers/1024
printf "Slab(可回收): %8d MB (dentry/inode缓存)\n", sreclaimable/1024
printf "Slab(不可回收): %8d MB\n", sunreclaim/1024
printf "内核栈: %8d MB\n", kernelstack/1024
printf "页表: %8d MB\n", pagetables/1024
printf "\n=== 可回收内存 ===\n"
printf "可回收总量约: %8d MB\n", (buffers+cached-shmem+sreclaimable)/1024
}
' /proc/meminfo
三、示例代码和配置
3.1 内核参数调优
vm.swappiness
这是一个控制内存回收策略的参数,范围 0-100(或者更高),表示内核倾向于回收匿名页面(swap out)还是文件页面(drop cache)的程度。
# 查看当前值
$ cat /proc/sys/vm/swappiness
60
# 临时修改
$ sysctl vm.swappiness=10
# 永久修改
$ echo"vm.swappiness = 10" >> /etc/sysctl.conf
$ sysctl -p
参数含义:
swappiness = 0:内核尽可能避免使用 swap,除非内存压力极大swappiness = 60:默认值,平衡回收匿名页和文件页swappiness = 100:积极使用 swap
生产环境建议:
- 数据库服务器(MySQL、PostgreSQL):1-10
- 需要大量文件缓存的场景(Elasticsearch):30-60
注意:swappiness = 0 不等于禁用 swap。在内存极度紧张时,内核仍然会使用 swap。如果真的不想用 swap,应该 swapoff -a。
vm.dirty_ratio 和 vm.dirty_background_ratio
这两个参数控制脏页(等待写入磁盘的数据)的比例。
# 查看当前值
$ sysctl vm.dirty_ratio vm.dirty_background_ratio
vm.dirty_ratio = 20
vm.dirty_background_ratio = 10
参数含义:
dirty_background_ratio:脏页达到内存的这个比例时,后台线程开始写回dirty_ratio:脏页达到内存的这个比例时,前台进程被阻塞,必须等待写回
生产配置示例:
# 适用于 SSD 磁盘的服务器
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500
# 适用于 HDD 或网络存储
vm.dirty_ratio = 40
vm.dirty_background_ratio = 10
vm.dirty_expire_centisecs = 6000
vm.dirty_writeback_centisecs = 1000
还有两个相关参数:
dirty_expire_centisecs:脏页过期时间(单位:厘秒),过期后会被写回dirty_writeback_centisecs:内核 flush 线程的唤醒间隔
vm.vfs_cache_pressure
控制内核回收 dentry 和 inode 缓存的倾向。
$ sysctl vm.vfs_cache_pressure
vm.vfs_cache_pressure = 100
生产建议:
- 文件数量多的场景(如 NFS 服务器):100-200
vm.min_free_kbytes
内核保留的最小空闲内存量。当空闲内存低于这个值时,内核会触发紧急回收。
# 查看当前值
$ sysctl vm.min_free_kbytes
vm.min_free_kbytes = 90112
# 计算建议值:sqrt(物理内存KB) * 16,但不超过物理内存的5%
# 对于 32GB 内存的机器,建议值约 90MB - 200MB
$ sysctl vm.min_free_kbytes=131072
这个参数设置太小会导致内存回收不及时,可能触发 OOM;设置太大会浪费内存。
完整的 sysctl 配置示例
# /etc/sysctl.d/99-memory-tuning.conf
# 内存管理优化配置
# Swap 使用策略
vm.swappiness = 10
# 脏页控制
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500
# VFS 缓存压力
vm.vfs_cache_pressure = 50
# 最小空闲内存(根据实际内存调整)
vm.min_free_kbytes = 131072
# 允许 overcommit(根据业务需求)
vm.overcommit_memory = 0
vm.overcommit_ratio = 50
# 内存压缩(内核 3.14+)
vm.compact_unevictable_allowed = 1
# 透明大页(根据应用选择,数据库通常禁用)
# kernel.transparent_hugepage = never
应用配置:
$ sysctl -p /etc/sysctl.d/99-memory-tuning.conf
3.2 OOM Killer 机制
当内存耗尽且 swap 也用完时,内核会触发 OOM Killer,选择并杀死一个或多个进程来释放内存。
OOM Score 计算
每个进程都有一个 oom_score,范围 0-1000,值越大越容易被杀。
# 查看所有进程的 oom_score
$ ps -eo pid,comm,oom_score | sort -k3 -nr | head -20
# 查看指定进程的 oom_score
$ cat /proc/$(pidof mysqld)/oom_score
oom_score 主要基于进程的内存使用量计算,内存用得越多,score 越高。
oom_score_adj 调整
oom_score_adj 允许手动调整进程的 oom_score,范围 -1000 到 +1000。
# 查看当前 oom_score_adj
$ cat /proc/$(pidof mysqld)/oom_score_adj
0
# 设置 oom_score_adj(需要 root 权限)
$ echo -500 > /proc/$(pidof mysqld)/oom_score_adj
特殊值:
-1000:完全禁止 OOM Killer 杀死这个进程(谨慎使用)
生产中的配置方式:
对于 systemd 管理的服务,在 service 文件中配置:
# /etc/systemd/system/myapp.service
[Service]
OOMScoreAdjust=-500
对于直接启动的进程,可以在启动脚本中设置:
#!/bin/bash
# 启动应用并调整 OOM 优先级
/usr/bin/myapp &
pid=$!
echo -500 > /proc/$pid/oom_score_adj
查看 OOM 日志
OOM 事件会记录在 dmesg 和 syslog 中:
# 查看 OOM 日志
$ dmesg | grep -i "oom\|killed process"
$ grep -i "oom" /var/log/messages
# 查看更详细的信息
$ dmesg | grep -A 50 "Out of memory"
典型的 OOM 日志:
[123456.789] Out of memory: Kill process 12345 (java) score 850 or sacrifice child
[123456.790] Killed process 12345 (java) total-vm:8234567kB, anon-rss:4567890kB, file-rss:12345kB
3.3 cgroups 内存限制
cgroups(Control Groups)提供了细粒度的资源限制能力。
cgroups v1 配置
# 创建 cgroup
$ mkdir /sys/fs/cgroup/memory/myapp
# 设置内存限制(4GB)
$ echo 4294967296 > /sys/fs/cgroup/memory/myapp/memory.limit_in_bytes
# 设置 swap 限制(内存+swap 总量,设置为与内存相同表示禁用 swap)
$ echo 4294967296 > /sys/fs/cgroup/memory/myapp/memory.memsw.limit_in_bytes
# 将进程加入 cgroup
$ echo 12345 > /sys/fs/cgroup/memory/myapp/cgroup.procs
# 查看当前内存使用
$ cat /sys/fs/cgroup/memory/myapp/memory.usage_in_bytes
cgroups v2 配置
cgroups v2 是统一的 cgroups 架构,配置方式有所不同:
# cgroups v2 中,内存限制在 memory.max 文件
$ mkdir /sys/fs/cgroup/myapp
# 设置内存限制
$ echo 4G > /sys/fs/cgroup/myapp/memory.max
# 设置软限制(超过这个值会触发回收但不会被杀)
$ echo 3G > /sys/fs/cgroup/myapp/memory.high
# 查看当前使用
$ cat /sys/fs/cgroup/myapp/memory.current
systemd 中的 cgroups 配置
对于 systemd 管理的服务,使用单元文件配置更方便:
# /etc/systemd/system/myapp.service
[Service]
# 内存限制
MemoryMax=4G
MemoryHigh=3G
# 禁用 swap(设置为 0)
MemorySwapMax=0
# 进程 OOM 优先级
OOMScoreAdjust=-500
# OOM 策略(continue/stop/kill)
OOMPolicy=continue
Docker/Kubernetes 中的内存限制
Docker 容器的内存限制本质上也是 cgroups:
# Docker 运行时限制内存
$ docker run -m 4g --memory-swap 4g myimage
# 查看容器的 cgroup 限制
$ cat /sys/fs/cgroup/memory/docker/<container-id>/memory.limit_in_bytes
Kubernetes Pod 配置:
apiVersion:v1
kind:Pod
spec:
containers:
-name:myapp
resources:
requests:
memory:"2Gi"
limits:
memory:"4Gi"
3.4 大页内存 (HugePages)
标准页面大小是 4KB,大页内存使用 2MB 或 1GB 的页面,可以减少 TLB miss,提升大内存应用的性能。
查看和配置
# 查看大页信息
$ grep -i huge /proc/meminfo
AnonHugePages: 2097152 kB
ShmemHugePages: 0 kB
HugePages_Total: 128
HugePages_Free: 64
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
# 配置大页数量(需要 root)
$ sysctl vm.nr_hugepages=128
# 永久配置
$ echo"vm.nr_hugepages = 128" >> /etc/sysctl.conf
应用使用大页
MySQL 配置:
# my.cnf
[mysqld]
large-pages
启动前需要确保:
# 给 mysql 用户权限
$ echo"mysql soft memlock unlimited" >> /etc/security/limits.conf
$ echo"mysql hard memlock unlimited" >> /etc/security/limits.conf
Java 应用配置:
# JVM 参数
$ java -XX:+UseLargePages -XX:LargePageSizeInBytes=2m -Xmx4g -jar myapp.jar
透明大页 (THP)
透明大页是内核自动管理的大页,应用不需要显式使用。但对于数据库等应用,THP 可能导致延迟抖动,通常建议禁用。
# 查看 THP 状态
$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
# 禁用 THP
$ echo never > /sys/kernel/mm/transparent_hugepage/enabled
$ echo never > /sys/kernel/mm/transparent_hugepage/defrag
# 永久禁用(GRUB)
# 在 /etc/default/grub 的 GRUB_CMDLINE_LINUX 中添加:
# transparent_hugepage=never
四、最佳实践和注意事项
4.1 内存监控的正确姿势
告警阈值设置
错误的告警规则:
# 不要这样做!
-alert:HighMemoryUsage
expr:(node_memory_MemTotal_bytes-node_memory_MemFree_bytes)/node_memory_MemTotal_bytes>0.9
正确的告警规则:
# Prometheus 告警规则
-alert:HighMemoryUsage
expr:(node_memory_MemTotal_bytes-node_memory_MemAvailable_bytes)/node_memory_MemTotal_bytes>0.85
for:5m
labels:
severity:warning
annotations:
summary:"内存可用率低于 15%"
-alert:CriticalMemoryUsage
expr:(node_memory_MemTotal_bytes-node_memory_MemAvailable_bytes)/node_memory_MemTotal_bytes>0.95
for:2m
labels:
severity:critical
annotations:
summary:"内存可用率低于 5%,可能触发 OOM"
应该监控的指标
# 基础指标
-node_memory_MemAvailable_bytes# 可用内存
-node_memory_SwapFree_bytes# Swap 剩余
-node_memory_MemFree_bytes# 空闲内存(参考)
# 趋势指标
-rate(node_vmstat_pgfault[5m])# 缺页错误率
-rate(node_vmstat_pgmajfault[5m])# 主缺页错误率(磁盘 I/O)
-rate(node_vmstat_pswpin[5m])# Swap in 速率
-rate(node_vmstat_pswpout[5m])# Swap out 速率
# OOM 相关
-increase(node_vmstat_oom_kill[1h])# OOM Kill 次数
4.2 手动释放缓存
可以手动释放缓存,但99%的情况下不应该这么做。
# 释放 page cache
$ sync; echo 1 > /proc/sys/vm/drop_caches
# 释放 dentries 和 inodes
$ sync; echo 2 > /proc/sys/vm/drop_caches
# 释放以上所有
$ sync; echo 3 > /proc/sys/vm/drop_caches
为什么不应该主动释放缓存?
- 缓存存在是为了提升性能,释放后会导致 I/O 压力增加
什么时候可以释放?
4.3 Swap 的合理使用
完全禁用 Swap 是一把双刃剑。
禁用 Swap 的好处:
禁用 Swap 的风险:
我的建议:
- 物理内存充足的服务器:配置少量 Swap(1-4GB),swappiness=1
- 内存紧张的服务器:配置与物理内存等量的 Swap,swappiness=10
4.4 内存过度分配 (Overcommit)
Linux 默认允许内存 overcommit,即进程申请的内存总量可以超过物理内存。
# 查看当前 overcommit 策略
$ sysctl vm.overcommit_memory
vm.overcommit_memory = 0
参数含义:
2:不允许 overcommit,申请超过 (swap + 物理内存 * overcommit_ratio%) 会失败
生产建议:
- Redis 等服务:需要设置为 1,否则 fork 进行 RDB 时可能失败
# Redis 官方建议配置
$ sysctl vm.overcommit_memory=1
五、故障排查和监控
5.1 内存泄漏排查
使用 pmap 查看进程内存映射
# 查看进程内存映射
$ pmap -x $(pidof myapp)
Address Kbytes RSS Dirty Mode Mapping
0000555555554000 132 84 0 r-x-- myapp
0000555555774000 8 8 8 r---- myapp
0000555555776000 4 4 4 rw--- myapp
...
# 查看堆内存
$ pmap -x $(pidof myapp) | grep heap
0000555555777000 65536 32768 32768 rw--- [ heap ]
使用 smaps 分析详细内存
# 查看进程详细内存信息
$ cat /proc/$(pidof myapp)/smaps
# 统计各类型内存
$ awk '/^Size:/{size+=$2} /^Rss:/{rss+=$2} /^Pss:/{pss+=$2} /^Shared_Clean:/{sc+=$2} /^Shared_Dirty:/{sd+=$2} /^Private_Clean:/{pc+=$2} /^Private_Dirty:/{pd+=$2} END{printf "Size: %d\nRss: %d\nPss: %d\nShared_Clean: %d\nShared_Dirty: %d\nPrivate_Clean: %d\nPrivate_Dirty: %d\n",size,rss,pss,sc,sd,pc,pd}' /proc/$(pidof myapp)/smaps
Valgrind 内存分析
Valgrind 是 Linux 下最强大的内存分析工具:
# 安装
$ yum install valgrind # CentOS
$ apt install valgrind # Ubuntu
# 检测内存泄漏
$ valgrind --leak-check=full --show-leak-kinds=all ./myapp
# 输出到文件
$ valgrind --leak-check=full --log-file=valgrind.log ./myapp
Valgrind 输出解读:
==12345== LEAK SUMMARY:
==12345== definitely lost: 1,234 bytes in 12 blocks
==12345== indirectly lost: 5,678 bytes in 56 blocks
==12345== possibly lost: 0 bytes in 0 blocks
==12345== still reachable: 9,012 bytes in 90 blocks
==12345== suppressed: 0 bytes in 0 blocks
indirectly lost:由于父块泄漏导致的泄漏possibly lost:可能的泄漏(存在指针,但可能是误报)still reachable:程序退出时仍有指针指向(通常不是问题)
jemalloc 内存分析
对于 C/C++ 程序,可以使用 jemalloc 替代系统 malloc,获得更好的分析能力:
# 安装 jemalloc
$ yum install jemalloc jemalloc-devel
# 使用 jemalloc 运行程序
$ LD_PRELOAD=/usr/lib64/libjemalloc.so.2 MALLOC_CONF="prof:true,prof_prefix:jeprof" ./myapp
# 生成分析报告
$ jeprof --show_bytes --pdf ./myapp jeprof.*.heap > memory.pdf
Java 应用内存分析
# 查看堆内存使用
$ jmap -heap $(pidof java)
# 导出堆转储
$ jmap -dump:format=b,file=heapdump.hprof $(pidof java)
# 分析堆转储(使用 MAT 或 jhat)
$ jhat heapdump.hprof
# 访问 http://localhost:7000
# 查看类实例统计
$ jmap -histo $(pidof java) | head -50
5.2 OOM 问题分析
收集 OOM 现场
# 配置内核在 OOM 时生成 dump
$ sysctl kernel.panic_on_oom=1 # 谨慎使用,会导致重启
# 更安全的方式:记录 OOM 时的内存状态
$ cat /proc/sys/kernel/sysrq
$ echo 1 > /proc/sys/kernel/sysrq # 启用 sysrq
分析 OOM 日志
# 提取 OOM 相关日志
$ dmesg -T | grep -A 100 "Out of memory"
# 关键信息解读
# 1. 触发 OOM 的进程
# 2. 当时的内存状态
# 3. 被杀的进程和原因
典型的 OOM 日志分析:
# 内存统计
[Mon Dec 25 10:00:00 2024] Node 0 active_anon:8234560kB inactive_anon:3234567kB active_file:10000000kB inactive_file:8000000kB unevictable:12345kB isolated(anon):0kB isolated(file):0kB mapped:987654kB dirty:12345kB writeback:0kB shmem:1234567kB...
# 被杀进程信息
[Mon Dec 25 10:00:00 2024] Out of memory: Kill process 12345 (java) score 850 or sacrifice child
[Mon Dec 25 10:00:00 2024] Killed process 12345 (java), UID 1000, total-vm:8234567kB, anon-rss:4567890kB, file-rss:12345kB
5.3 持续监控方案
使用 Prometheus + Grafana
node_exporter 配置要点:
# node_exporter 默认已包含内存指标
# 确保以下 collector 启用
--collector.meminfo
--collector.vmstat
Grafana Dashboard 关键面板:
# 可用内存百分比
100 - ((node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100)
# 内存压力(综合指标)
rate(node_vmstat_pgmajfault[5m]) + rate(node_vmstat_pswpin[5m]) + rate(node_vmstat_pswpout[5m])
# 缓存命中率估算
1 - (rate(node_vmstat_pgmajfault[5m]) / (rate(node_vmstat_pgfault[5m]) + 0.001))
使用 eBPF 进行深度分析
bpftrace 是现代 Linux 内存分析的利器:
# 安装 bpftrace
$ yum install bpftrace # CentOS 8+
$ apt install bpftrace # Ubuntu 19.04+
# 跟踪 OOM 事件
$ bpftrace -e 'kprobe:oom_kill_process { printf("OOM kill: %s\n", comm); }'
# 跟踪大内存分配
$ bpftrace -e 'tracepoint:kmem:mm_page_alloc { @bytes = hist(args->order); }'
# 跟踪缺页错误
$ bpftrace -e 'kprobe:handle_mm_fault { @[comm] = count(); }'
六、总结
Linux 内存管理是一个复杂但有规律可循的领域。核心要点:
1. 正确理解内存使用率
available 才是真正可用的内存,不要被 free 值吓到。Buffer 和 Cache 是好东西,不要随便清除。
2. 参数调优要因地制宜
- 数据库服务器:低 swappiness,禁用 THP
- 应用服务器:适度 swappiness,保留足够 cache
3. OOM 不是洪水猛兽
合理配置 oom_score_adj,让内核杀对的进程。监控 OOM 事件,分析根因。
4. 监控先于调优
在调优之前,先建立完善的监控体系。知道问题在哪,才能对症下药。
5. 稳定性优先
生产环境的内存调优要保守,每次只改一个参数,观察稳定后再进行下一步。
附录:命令速查表
内存查看命令
| | |
|---|
free -h | | free -h |
cat /proc/meminfo | | grep MemAvailable /proc/meminfo |
vmstat 1 | | vmstat 1 10 |
top | | top -o %MEM |
ps aux --sort=-%mem | | ps aux --sort=-%mem | head |
pmap -x PID | | pmap -x $(pidof nginx) |
smem | | smem -kt |
内核参数配置
| | | |
|---|
vm.swappiness | | | |
vm.dirty_ratio | | | |
vm.dirty_background_ratio | | | |
vm.vfs_cache_pressure | | | |
vm.min_free_kbytes | | | |
vm.overcommit_memory | | | |
cgroups 内存控制
| | |
|---|
memory.limit_in_bytes | memory.max | |
memory.soft_limit_in_bytes | memory.high | |
memory.memsw.limit_in_bytes | memory.swap.max | |
memory.usage_in_bytes | memory.current | |
memory.oom_control | | |
故障排查工具
| | |
|---|
valgrind | | yum install valgrind |
jemalloc | | yum install jemalloc |
perf | | yum install perf |
bpftrace | | yum install bpftrace |
jmap | | |
gdb | | yum install gdb |
常用诊断命令组合
# 内存去向快速分析
$ awk '/MemTotal/{t=$2} /MemAvailable/{a=$2} /AnonPages/{anon=$2} /Cached/{c=$2} /Buffers/{b=$2} /Slab/{s=$2} END{printf "Total: %.1fG\nAvailable: %.1fG\nAnon: %.1fG\nCache: %.1fG\nBuffer: %.1fG\nSlab: %.1fG\n",t/1024/1024,a/1024/1024,anon/1024/1024,c/1024/1024,b/1024/1024,s/1024/1024}' /proc/meminfo
# 找出内存占用最多的进程
$ ps aux --sort=-%mem | head -20
# 查看进程的 OOM 分数
$ for pid in $(pgrep -f "java\|nginx\|mysql"); doecho"PID: $pid$(cat /proc/$pid/comm) OOM: $(cat /proc/$pid/oom_score)"; done
# 查看 Slab 缓存详情
$ slabtop -o | head -20
# 实时监控内存压力
$ watch -n 1 'grep -E "MemAvailable|SwapFree|Dirty" /proc/meminfo'
参考文档:
- Linux Kernel Documentation: https://www.kernel.org/doc/Documentation/admin-guide/mm/
- Red Hat Memory Management: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/performance_tuning_guide/sect-red_hat_enterprise_linux-performance_tuning_guide-memory
- Brendan Gregg's Blog: https://www.brendangregg.com/blog/
文末福利
网络监控是保障网络系统和数据安全的重要手段,能够帮助运维人员及时发现并应对各种问题,及时发现并解决,从而确保网络的顺畅运行。
谢谢一路支持,给大家分享6款开源免费的网络监控工具,并准备了对应的资料文档,建议运维工程师收藏(文末一键领取)。

100%免费领取
一、zabbix
二、Prometheus
内容较多,6款常用网络监控工具(zabbix、Prometheus、Cacti、Grafana、OpenNMS、Nagios)不再一一介绍, 需要的朋友扫码备注【监控合集】,即可100%免费领取。
以上所有资料获取请扫码

100%免费领取
(后台不再回复,扫码一键领取)