评估内存3
Linux 内存健康评估脚本(进阶版)完整文档
0. 文档定位与目标
本文档用于配合并指导使用 Linux 内存健康评估 — 终极全功能脚本(进阶增强版),目标读者为:
-
SRE / 运维工程师
-
内核 / 性能工程师
-
容器 / 云平台维护人员
目标不是“查看内存”,而是:
在 OOM、性能抖动、软中断异常、kswapd 飙高之前,定位内存结构性风险与根因方向。
1. Linux 内存全景模型(脚本设计基础)
1.1 内存分类(逻辑视角)
物理内存
├─ 用户态
│ ├─ Anonymous (Active / Inactive)
│ └─ Swap-backed
├─ PageCache / Cached(文件内容)
├─ Buffers(块设备元数据)
├─ Slab(内核对象)
│ ├─ SReclaimable
│ └─ SUnreclaimable
└─ 内核其他(stack / pagetables / vmalloc)
1.2 本脚本的核心思想
-
不看“用了多少”,看“谁在用 + 能不能回收”
-
通过 比例 + 结构 + 行为指标 判定风险
-
避免被
free -h/top误导
2. Slab 内存(内核对象)的进阶解读
2.1 Slab 是什么
Slab 是 Linux 内核的对象级内存分配器(kmem_cache),用于高频、固定大小内核对象。
典型对象:
-
socket / sk_buff
-
inode / dentry
-
task_struct
-
协议栈、文件系统元数据
2.2 SReclaimable vs SUnreclaimable
| 类型 | 含义 | 风险等级 |
|---|---|---|
| SReclaimable | 可回收对象(inode/dentry) | 低 |
| SUnreclaimable | 不可回收对象(socket/task) | 高 |
SUnreclaimable 是“真正的内核常驻内存”
2.3 脚本中的 Slab 判定逻辑
-
Slab 占比(Slab / MemTotal)
-
SUnreclaimable / Slab 比例
-
Slab TopN 集中度(是否单一子系统异常)
风险经验阈值
| 指标 | 正常 | 关注 | 危险 |
|---|---|---|---|
| Slab% | <10% | 10–20% | >20% |
| SUnreclaimable / Slab | <30% | 30–50% | >50% |
| Top3 集中度 | <40% | 40–60% | >60% |
3. 高阶内存 / 低阶内存(碎片风险)
3.1 Buddy System 与 order
Linux 物理内存按 2^order 页分配:
| Order | 页数 | 大小 |
|---|---|---|
| 0 | 1 | 4KB |
| 1 | 2 | 8KB |
| 2 | 4 | 16KB |
| ≥3 | ≥8 | ≥32KB |
3.2 高阶内存的危险性
-
需要连续物理页
-
易受内存碎片影响
-
常见失败场景:
-
THP 分配
-
DMA
-
网络大包
-
3.3 脚本如何检测碎片
-
/proc/buddyinfo中 order>=3 可用页 -
kswapd CPU 占用
-
pgscan / pgsteal 比例
MemAvailable 高 ≠ 高阶内存充足
4. Buffer / Cache 的正确理解
4.1 Buffer
-
块设备元数据缓存
-
文件系统结构信息
-
正常系统中占比极小(<5%)
4.2 Cache / PageCache
-
文件内容缓存
-
mmap 文件页
-
可随时回收
PageCache 高通常是“健康状态”,不是问题
4.3 Cache 风险判断
真正的风险不是 Cache 大,而是:
-
Cache 被频繁扫描(pgscan 高)
-
但难以回收(pgsteal 低)
这意味着 Cache 被 匿名内存或 Slab 挤压。
5. 匿名内存(Anon)与 OOM 风险
5.1 Anon 是什么
-
进程堆、栈、匿名 mmap
-
OOM Killer 的主要目标
5.2 风险信号
| 指标 | 含义 |
|---|---|
| Anon >60% | OOM 高风险 |
| Active(anon) 高 | 回收困难 |
| Swap 使用 | 内存压力已发生 |
6. 回收行为与压力指标(行为层)
6.1 kswapd
-
后台回收线程
-
CPU 高 → 内存压力显著
6.2 vmstat 关键字段
| 字段 | 含义 |
|---|---|
| pgscan_* | 扫描页数 |
| pgsteal_* | 回收页数 |
| pgmajfault | 重大缺页 |
6.3 PSI(Pressure Stall Information)
-
some:部分任务受阻
-
full:系统级内存阻塞
用于判断“是否真的卡住”。
7. 容器 / cgroup 视角
7.1 cgroup v1 / v2
-
memory.current / memory.max
-
memory.stat(anon / file / slab)
7.2 常见误区
-
容器 OOM,但宿主 MemAvailable 充足
-
slab 不受 limit 直接限制(部分场景)
8. 自动判因逻辑(脚本核心价值)
脚本会根据多指标组合,给出根因方向:
-
匿名内存主导
-
Slab 不可回收对象
-
PageCache 被挤压
-
高阶内存碎片
这是“专家经验”的程序化表达。
9. 典型故障场景对照表
| 现象 | 高概率原因 |
|---|---|
| MemAvail 低 + Anon 高 | 应用内存泄漏 |
| Slab 高 + SUn 高 | 网络 / 内核对象泄漏 |
| Swap 用但 Cache 高 | 匿名内存挤压 |
| OOM 无预警 | 高阶内存碎片 |
10. 使用建议与下一步
10.1 日常巡检
-
定时执行脚本
-
对比 Slab / Anon 增长趋势
10.2 出现异常后
-
slabtop / perf / smem / pmap
-
定位具体进程或子系统
10.3 可扩展方向
-
时间序列记录(泄漏检测)
-
PID 级匿名内存归因
-
K8s OOM 预测模型
11. 总结
这套脚本与文档的目标是:
让内存问题从“经验判断”变成“结构化诊断”。
它适用于:
-
裸机
-
虚拟机
-
容器
-
生产事故前与事故中
#!/usr/bin/env bash
# ==========================================================
# Linux 内存健康评估 — 终极进阶增强版(Production)
# ==========================================================
set -euo pipefail
IFS=$'\n\t'
# ---------------- Config ----------------
N=10
MEMINFO=/proc/meminfo
SLABINFO=/proc/slabinfo
CGROUP_V1=/sys/fs/cgroup/memory
CGROUP_ROOT=/sys/fs/cgroup
CHECK_NUMA=1
# ---------------- Colors ----------------
color(){ case "$1" in
green) echo -e "\033[32m$2\033[0m";;
yellow) echo -e "\033[33m$2\033[0m";;
red) echo -e "\033[31m$2\033[0m";;
bold) echo -e "\033[1m$2\033[0m";;
*) echo "$2";;
esac }
# ---------------- Helpers ----------------
has_cmd(){ command -v "$1" >/dev/null 2>&1; }
kb_to_gb(){ awk -v v="$1" 'BEGIN{printf "%.2f", v/1024/1024}'; }
safe_read(){ [[ -f "$1" ]] && cat "$1" 2>/dev/null || echo 0; }
get_field(){
local key="$1"
awk -v k="$key" '$1 ~ "^"k":" {for(i=1;i<=NF;i++){if($i~/^[0-9]+$/){print $i; exit}}}' "$MEMINFO"
}
num(){ [[ -z "${1:-}" ]] && echo 0 || echo "$1"; }
pct(){ awk -v a="$1" -v b="$2" 'BEGIN{if(b==0){print 0}else{printf "%.1f", a/b*100}}'; }
# ---------------- 浮点比较函数 ----------------
f_lt() { awk -v a="$1" -v b="$2" 'BEGIN{exit !(a<b)}'; }
f_gt() { awk -v a="$1" -v b="$2" 'BEGIN{exit !(a>b)}'; }
judge(){
val="$1"; g="$2"; y="$3"; u="${4:-}"
cmp=$(awk -v v="$val" -v g="$g" -v y="$y" 'BEGIN{if(v<g)print "G";else if(v<y)print "Y";else print "R"}')
case "$cmp" in
G) color green "${val}${u} (✔ 正常)";;
Y) color yellow "${val}${u} (⚠ 关注)";;
R) color red "${val}${u} (✘ 危险)";;
esac
}
# ---------------- Read meminfo ----------------
MemTotal=$(num "$(get_field MemTotal)")
MemAvailable=$(num "$(get_field MemAvailable)")
Cached=$(num "$(get_field Cached)")
Buffers=$(num "$(get_field Buffers)")
Slab=$(num "$(get_field Slab)")
SReclaimable=$(num "$(get_field SReclaimable)")
SUnreclaimable=$(num "$(get_field SUnreclaim)")
ActiveAnon=$(num "$(get_field Active_anon)")
InactiveAnon=$(num "$(get_field Inactive_anon)")
SwapTotal=$(num "$(get_field SwapTotal)")
SwapFree=$(num "$(get_field SwapFree)")
PageTables=$(num "$(get_field PageTables)")
KernelStack=$(num "$(get_field KernelStack)")
Shmem=$(num "$(get_field Shmem)")
AnonTotal=$((ActiveAnon + InactiveAnon))
SwapUsed=$((SwapTotal - SwapFree))
PageCache=$Cached
kswapd_cpu=$(ps -eo comm,pcpu 2>/dev/null | awk '/kswapd/ {sum+=$2} END{printf "%.1f",sum+0}')
PageCachePct=$(pct $PageCache $MemTotal)
AnonPct=$(pct $AnonTotal $MemTotal)
SlabPct=$(pct $Slab $MemTotal)
MemAvailPct=$(pct $MemAvailable $MemTotal)
# ---------------- Header ----------------
echo "===================== Linux 内存健康评估(终极进阶版) ====================="
echo "总内存: $(kb_to_gb $MemTotal) GB"
echo -n "可用内存: "; judge $MemAvailPct 20 10 "%"
# ==========================================================
# Slab 深度分析
# ==========================================================
echo "▶ Slab 内存(内核对象)"
echo -n "Slab 占比: "; judge $SlabPct 10 20 "%"
SUnPct=$(pct $SUnreclaimable $Slab)
echo -n "SUnreclaimable / Slab: "; judge $SUnPct 30 50 "%"
if [[ -f $SLABINFO ]]; then
echo
echo "Slab Top $N(按占用):"
awk 'NR>2{print $1,$2*$3}' "$SLABINFO" | sort -k2 -nr | head -n $N
fi
# ==========================================================
# PageCache / Buffer 行为分析
# ==========================================================
echo
echo "▶ PageCache / Buffers"
echo -n "PageCache 占比: "; judge $PageCachePct 50 70 "%"
echo -n "Buffers 占比: "; judge $(pct $Buffers $MemTotal) 5 10 "%"
if [[ -f /proc/vmstat ]]; then
pgscan=$(awk '/pgscan_kswapd/ {print $2}' /proc/vmstat)
pgsteal=$(awk '/pgsteal_kswapd/ {print $2}' /proc/vmstat)
echo "pgscan_kswapd=$pgscan pgsteal_kswapd=$pgsteal"
if (( pgscan > pgsteal * 2 )); then
color red "PageCache 被频繁扫描但难以回收(被挤压)"
fi
fi
# ==========================================================
# 匿名内存 & OOM 风险
# ==========================================================
echo
echo "▶ 匿名内存(Anon)"
echo -n "Anon 占比: "; judge $AnonPct 50 70 "%"
if f_gt "$ActiveAnon" "$((MemTotal/2))"; then
color red "Active(anon) >50%,OOM 高风险"
fi
# ==========================================================
# Swap & kswapd
# ==========================================================
echo
echo "▶ Swap"
SwapMB=$((SwapUsed/1024))
if (( SwapMB==0 )); then
color green "Swap 未使用"
else
color yellow "SwapUsed=${SwapMB}MB"
fi
echo
echo "▶ kswapd CPU"
if f_lt "$kswapd_cpu" 3; then
color green "$kswapd_cpu%"
elif f_lt "$kswapd_cpu" 5; then
color yellow "$kswapd_cpu%"
else
color red "$kswapd_cpu%"
fi
# ==========================================================
# 高阶内存 / 碎片风险
# ==========================================================
echo
echo "▶ 高阶内存 / 碎片风险"
if [[ -f /proc/buddyinfo ]]; then
HighOrderFree=$(awk '{for(i=6;i<=NF;i++){if(i-6>=3)sum+=$i}} END{print sum+0}' /proc/buddyinfo)
echo "High-order free pages: $HighOrderFree"
if (( HighOrderFree < 100 )); then
color red "高阶连续页严重不足(THP / DMA / 网络风险)"
elif (( HighOrderFree < 500 )); then
color yellow "高阶连续页偏少(碎片化)"
else
color green "高阶内存充足"
fi
fi
# ==========================================================
# PSI
# ==========================================================
echo
echo "▶ PSI (memory)"
[[ -f /proc/pressure/memory ]] && cat /proc/pressure/memory
# ==========================================================
# 自动判因
# ==========================================================
echo
echo "===================== 自动判因结论 ====================="
if f_lt "$MemAvailPct" 10 && f_gt "$AnonPct" 60; then
echo "- 根因方向:匿名内存(进程占用)"
elif f_gt "$SlabPct" 20 && f_gt "$SUnPct" 50; then
echo "- 根因方向:Slab 不可回收对象(内核/网络/驱动)"
elif f_gt "$PageCachePct" 60 && (( pgscan > pgsteal )); then
echo "- 根因方向:PageCache 被挤压"
elif (( HighOrderFree < 100 )); then
echo "- 根因方向:内存碎片(高阶分配失败)"
else
echo "- 当前未发现明确单点内存瓶颈"
fi
echo "===================== 评估完成 ====================="
(完)
浙公网安备 33010602011771号