性能测试内存分析:free、vmstat、sar命令实战解读
做性能测试的时候内存问题大概是所有指标里最容易被误读的。之前帮一个团队排查压测过程中响应时间飙升他们拿着free -h的结果说“内存还剩 6G肯定不是内存的锅”但实际应用日志里全是 GC 停顿vmstat的wa高得离谱页缓存被反复回收。问题恰恰就出在他们只看free的“可用内存”而没有理解 Linux 内存管理的真实逻辑。今天这篇内容就把free、vmstat、sar这三个命令掰开揉碎结合性能测试场景聊明白它们输出里的数字到底在说什么以及拿到一份内存数据后该怎么判断业务到底有没有问题。1. 性能测试内存分析先把思路理清楚1.1 性能测试时为什么要做内存分析性能测试的核心是确认系统在预期负载下能不能稳定服务而内存状态直接影响响应时间、吞吐量和稳定性。内存不足会导致频繁换页、触发 OOM、引发进程被杀或者让垃圾回收变成性能瓶颈。更隐蔽的是内存问题往往不是突然发生的而是随着压测时间推移逐步恶化的比如内存泄漏、缓存逐渐膨胀、分配器碎片化。如果只测几分钟内存问题基本不会暴露只有持续压测并用正确的工具观察趋势才能抓到那些“慢慢长起来”的病。我在实际项目中一般把内存分析放在这样几个节点压测前确认基线状态看内存分配策略、缓存占用、有没有明显异常进程。压测中按固定间隔采样重点看可用内存变化、swap 行为、CPU 等待和系统调用。压测后对比压测前后的指标计算是否有内存“只涨不跌”的情况判断是否存在泄漏。这三个阶段对应的问题不同使用的命令组合也不一样。很多人上来就free -h看一眼便下结论这恰恰是误判的开始。1.2 三种典型内存问题的判断路径性能测试里遇到的内存问题基本可以归为三类第一类物理内存确实不够用。系统可用内存持续走低swap 开始有进出流量进程响应变慢甚至触发 OOM。这类问题最容易判断但也最容易误判因为free输出的available和“物理还剩多少”并不是一回事。可用内存不足不代表真的不够得看具体的压力信号。第二类进程内部内存膨胀或泄漏。系统层面看 free 可能还正常但某个 worker 线程或 Java 进程堆外内存持续增长最终导致整体内存吃紧。这类问题需要结合/proc/pid/status、smaps或者应用侧的堆内存监控来判断单靠free是看不到的。第三类内存回收引发的次生问题。比如 page cache 占了很多内存一旦业务需要申请大量内存内核触发回收导致 IO 等待上升CPU 的wa飙升响应时间瞬间抖动。这种问题往往被误认为是磁盘或网卡问题实际根因是内存回收风暴。针对这三类我建议先根据现场现象选一个切入点再用命令去验证不要一上来把所有工具的输出都打印出来。后面的实操部分会按这个思路展开。2. 核心命令逐个拆解free、vmstat、sar 到底在看什么2.1 free 的真实含义不是剩下多少而是能安全用多少free是最常用的内存查看命令但大部分人的解读都有偏差。看一个典型的输出total used free shared buff/cache available Mem: 31G 12G 8.2G 123M 11G 17G Swap: 8G 0B 8.0G很多人会盯着free列看到 8.2G 觉得“内存还很多”或者看到used12G 觉得“用了一半多”。实际上这两个数字都容易误导。buff/cache占用的 11G 里绝大多数是 page cache也就是内核用来缓存文件数据的可回收内存。当业务进程需要内存时内核可以把这些缓存回收再分配出去所以真正值得关注的是available这一列它表示“在不触发 swap 的情况下预计可以分配给新进程的内存量”。free命令从 procfs 读取/proc/meminfo中的数据available的估算逻辑在不同内核版本里略有差异但它会考虑 page cache、slab 可回收部分以及内存水位。判断内存是否紧张第一眼应该看available而不是free。举一个真实例子。压测时 Java 服务占用了 16G 堆系统总内存 32Gfree显示只有 2G但available还有 9G这时候完全不需要担心物理内存不够。反过来如果available低于总内存的 10%同时 swap 的si开始增长那才说明真的进入紧张状态。2.2 vmstat 的 si/soswap 进出背后的内存压力信号vmstat是很老牌的系统监控工具输出分两大部分procs、memory、swap、io、system、cpu。性能测试中我常关注的是这几列procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 5 1 2048 102400 104960 8012032 23 17 32 24 123 456 23 67 3 7 0第一行是自上次开机以来的平均值第二行如果有两个采样间隔是当前采样周期内的变化。很多人只看swpd觉得没有交换出去很多就没问题实际上swpd只是已经换出的总量不代表当前压力。真正要盯的是si和so分别表示每秒从 swap 换入和换出的内存量。如果si和so持续大于 0说明物理内存已经不够用了内核在频繁地做页面换入换出。这往往会导致进程访问内存时出现缺页中断响应时间大幅抖动。注意即使si/so为 0也不代表没有内存压力因为系统可能只是还没走到 swap 那一步而是在频繁回收 page cache。这个时候要结合cache列的变化和cs上下文切换来判断。在性能测试现场我会用类似这样的命令每 5 秒采样一次vmstat 5 120如果发现si从 0 变成持续有值立刻标记为严重问题同时去看sar -B的输出确认是不是内存回收导致的。需要提醒的是vmstat输出里的free和free命令虽然都叫 free但取值可能略有差异因为采样时刻不同这不重要别在这种地方纠结。2.3 sar 历史记录从单点快照到趋势分析free和vmstat给的是某个瞬间的快照而内存问题往往是趋势性的。sar的优势在于可以收集历史数据事后回溯压测全过程。默认情况下sysstat 会定期把系统状态写入/var/log/sysstat/可以用sar直接查看历史。内存分析常用的是这几类参数sar -r # 查看内存使用率、可用内存、缓存情况 sar -B # 查看页交换、页回收等内核内存事件 sar -S # 查看 swap 空间使用情况 sar -q # 查看负载和运行队列长度sar -r的输出里有一个指标叫kbmemfree和kbmemused它反映的是物理内存层面的统计。重点看kbmemavail如果内核版本支持和kbcached。我之前排查过一个压测场景应用内存占用曲线在 20 分钟内从 4G 涨到 12Gfree快照看起来还能接受但sar -r的时间序列图一眼就能看出斜率异常。这就是历史数据的价值让你能区分“瞬时抖动”和“持续增长”。如果 sysstat 没有安装或者没开收集补救办法是用nohup后台跑一个循环采样脚本自己把vmstat或/proc/meminfo的信息落盘。压测结束再分析。我自己的习惯是压测前先确认sar的收集任务是否在运行参考标准是查看/etc/cron.d/sysstat很多服务器上默认只收集但没启用或者收集间隔太长10 分钟一次完全不够看。正常的配置应该每分钟收集一次压测期间可以手动缩短采样间隔。3. 实操组合一次完整的服务压测内存分析3.1 压测前基线采集内存状态、分配器、大页设置在压测开始前我会先花 5 分钟把系统的“出厂状态”记录清楚。不要直接开始压测否则后面的数据没有对照。需要记录的基线包括可用内存和 cache 占用free -h当前内存回收状态cat /proc/meminfo里的Dirty、Writeback值是否开启了透明大页cat /sys/kernel/mm/transparent_hugepage/enabledSwap 当前使用情况swapon --show关键进程的 RSS 基线ps -o pid,rss,cmd -p pid为什么要看透明大页因为它直接影响内存分配和释放行为。某些场景下HugePages 会导致页表占用膨胀也会让内存回收变得笨重对于追求稳定低延迟的服务通常建议关闭透明大页改成显式大页。压测前如果发现系统自动开启了 THP而你们跑的是 Java 或内存密集型服务建议先和运维沟通别在测试中临时改。基线采集还有一个容易被忽略的项内存分配器glibc malloc、jemalloc、tcmalloc。如果服务是 C/C 编写的或者 Java 启用了 Native Memory Tracking压测前最好确认使用的分配器类型。jemalloc 和 tcmalloc 对碎片化处理差异很大这些会影响进程 RSS 的趋势曲线。第一次压测前可以先不做深挖但基线里至少得记下来否则后面分析 RSS 一直涨时会搞不清楚是泄漏还是碎片。3.2 压测中实时监控组合命令与关键阈值压测进行中我不会只开一个top或free而是组合使用多个工具并记录每一时刻的系统事件。推荐的组合# 终端 1每 2 秒输出内存和 swap 状态 vmstat 2 300 # 终端 2每 5 秒输出系统整体内存概况 free -h -s 5 # 终端 3每 10 秒记录目标进程的状态 cat /proc/pid/status | grep -E VmRSS|VmSize|VmSwap /tmp/mem_$$.log同时用sar -r -B 2 300在后台记录更细的数据方便压测后画图。实时监控时我的阈值经验是这样的指标关注阈值说明available 内存低于总内存 10%进入警戒可能出现回收或 swapsi/so持续大于 0物理内存不足已开始换页wa 或 cs 异常升高相比基线翻倍可能是内存回收或分配触发的问题进程 RSS 增长率每 10 分钟超过 5%需要关注是否存在持续增长cache 抖动短时间内大量上升或下降可能伴随 IO 抖动注意磁盘响应需要特别说明的是这些阈值不是绝对的和业务特征强相关。比如一个服务本身就需要常驻 20G 的缓存那么 available 低是正常现象重点看进程的 RSS 是否符合预期。所谓“报警阈值”要基于业务基线和历史数据来定不要死记。实操中我会在压测脚本里加入一个“内存快照”动作每当并发数到达某个阶段就自动记录当时的free -h、vmstat 1 5、以及应用 GC 日志的最后几行。这样后续复盘时能精确到“并发从 500 升到 800 时内存发生了什么变化”。3.3 压测后数据处理快速判断是否存在内存泄漏压测结束后典型做法是把采集的历史数据整理成曲线。这里分享一个土办法不需要额外搭监控平台就能判断是否存在明显的内存泄漏取长时间压测下的进程 RSS 序列用最小二乘法拟合趋势线看斜率是否持续大于 0。我用 Script 脚本做过类似计算思路很简单把VmRSS的记录按时间排序计算相邻点的差值和增长速率。如果 RSS 一直上升且没有伴随缓存增长的合理解释就可以初步怀疑泄漏。一个可靠做法是分段对比前三分之一时间的平均 RSS 和后三分之一对比如果后者高 20% 以上就需要进一步分析了。注意单次压测的波动可能来自 GC 或缓存预热所以至少要压测 30 分钟以上才谈得上“趋势”。还有一种情况是系统 free 内存稳定但某个进程 RSS 在上升同时另一个进程被 OOM 杀掉。这往往是进程间的内存分布不均类似 Node.js 多实例应用或 Java 多 Pod 部署时某个实例负载不均导致内存被挤爆。这时候要用ps -o rss对所有相关进程做排序找出异常个体。压测后的列表脚本我经常这样用ps -eo pid,ppid,rss,vsz,comm,args --sort-rss | head -20如果 RSS 最高的进程恰好是压测目标服务里的某个子进程那就去查它的/proc/pid/smaps看是哪类内存段在增长。下面会展开讲。4. 内存泄漏排查实录那些容易误判的场景4.1 free 不降但 worker 内存升高这个场景很经典。压测时系统free始终稳定在 10G 左右看起来毫无压力但 Java 进程 RSS 从 2G 一路涨到 9G最终把可用内存压低触发 GC 频率上升。我遇到过一次真实的 case一个 Java 网关服务使用了堆外缓存框架但压测脚本没有真正释放连接池里的对象导致堆外内存不断增长。从free的角度完全看不出异常因为 page cache 在同步下降实际上是把缓存挤掉了。之后服务开始频繁 minor GC因为ConcMarkSweepGC在遇到堆外分配压力时也会影响主线程。排查路径先确认哪个进程在涨top -o %MEM排序。再确认进程内部是堆内还是堆外jmap -heap pid看堆占用如果堆稳定但 RSS 涨基本就是堆外或 native 内存。进一步用pmap -x pid看地址空间分布。使用grep VmRSS /proc/pid/status做持续采样观察增长速率。如果在容器里还要注意/sys/fs/cgroup下内存事件区分 cgroup 限制与宿主机整体压力。这个案例最后定位到连接池的idleTimeout配置错误导致每个请求都往堆外缓存里塞了一个大对象。修完配置后 RSS 曲线马上平了。性能测试中如果只盯着系统 free这类问题永远发现不了。4.2 swap 突然出现又消失还有一种容易误判的情况压测开始后一段时间vmstat里si/so从 0 变成偶尔有值但一会儿又归零free显示 available 还不少。很多人以为系统只是“偶尔抖动”忽略了背后的回收动作。这种情况下我会立刻去查sar -B里的pgscankkswapd 页面扫描速率和pgsteal页面回收速率。如果扫描速率突然飙升说明系统虽然 available 看起来还可以但某个瞬间大量页面被标记为可回收kswapd 被唤醒开始扫描并回收。这会导致瞬间 CPU 系统态升高进程分配内存出现延迟响应时间出现尖刺。实际场景是某服务压测中每隔 10 分钟出现一次毫秒级超时free和vmstat都没有明显变化。后来我把sar -B的数据拉出来发现每次超时前pgscank都会飙到几倍于峰值说明内核正在批量回收到期页面。系统的缓存策略是周期性清理碰上业务请求高峰时回收动作与业务叠加造成了延迟抖动。这个问题的解法可能要从缓存配置、应用层缓存 TTL 或者内核vm.vfs_cache_pressure参数入手而不是盲目加大内存。所以看到 swap 进出“来去无踪”不要轻易放过一定要找历史记录里的瞬时事件。这也说明实时采集务必保留高精度数据监控间隔超过 5 秒很容易漏掉这类尖峰。4.3 内存回收导致 CPU 冲高内存回收不仅影响响应时间也会影响 CPU 使用率。free显示 available 在下降但整体 CPU 还有大量空闲如果系统态 CPU 占比异常高并且cs上下文切换次数猛增那很可能就是页回收和分配锁竞争引起的。一个印象深刻的例子某压测环境有 128G 内存服务本身只用了 30G但 CPU 系统态占到 40%导致吞吐量上不去。排查发现系统开启了 THP应用程序出现了大量mmap_sem锁竞争线程在分配内存时互相阻塞。用sar -B看faults中majflt非常高再用perf抓调用栈能看到大量do_huge_pmd_page相关的函数。这类问题不是靠调大内存能解决的而是要考虑关闭 THPecho never /sys/kernel/mm/transparent_hugepage/enabled调整内存分配策略使用可预测的显式大页这里想强调一个实操习惯遇到 CPU 和内存指标都异常不要只归因到某一项要结合vmstat的cs、us、sy以及sar -B的faults综合判断。内存回收导致 CPU 冲高症状是sy升高但对应的us不一定高这可以作为一个识别特征。4.4 /proc/pid 与 smaps 的精确定位方法当怀疑某个进程内存异常时/proc文件系统是最直接的证据来源。我最常看这几个文件/proc/pid/status进程的VmRSS、VmSize、VmSwap、Threads/proc/pid/smaps进程地址空间中每个映射的详细内存信息/proc/pid/smaps_rollup汇总信息比 smaps 更快/proc/pid/maps地址段布局用来对应代码、堆、栈、共享库等如果进程 RSS 一直在涨可以用smaps_rollup快速看是哪一类地址段在增长。例如grep -E ^(Size|Rss|Pss|Shared_Clean|Shared_Dirty|Private_Clean|Private_Dirty) /proc/pid/smaps_rollup还可以按 address 分类统计 Rss 最大的几个 mapawk /^[0-9a-f]/ {map$0; next} /Rss:/ {rss$2; if (rss 10000) print rss, map} /proc/pid/smaps | sort -n -r | head -20这种文件的解读需要一些经验建议在测试环境多对比几个正常进程和异常进程。比如 JVM 的pthread栈通常分布在7f...起始的地址区如果是mmap动态分配的大量匿名映射持续增长方向就是堆外内存或线程栈。还要警惕VmSwap这一项它代表进程有多少内存被换出。VmSwap一直大于 0 且增长表示进程的部分内存已经不在物理内存里性能下降是可预期的这时重点要分析为什么会被换出——是系统整体内存不足还是进程的某些冷数据被内核主动回收。5. 常见问题与避坑指南5.1 问题速查表把前面提到的经验整理成一张速查表压测时可以直接对照现象首选排查命令判断要点常见处置available 持续低于 10%free -h、sar -r是否伴随 si/so 增长调整内存配额、优化缓存、增加内存swap 有进出但 available 不低vmstat、sar -B检查 pgscank 是否有尖峰调整回收参数、应用侧缓存策略进程 RSS 持续增长/proc/pid/status区分堆内/堆外增长结合应用日志定位占用源系统 CPU 高但业务负载低vmstat、sar -qcs 飙升、majflt 高检查 THP、内存分配器、锁竞争压测结束后内存不回落free、ps排序是否存在僵尸进程或缓存未释放重启验证是泄漏还是进程未退出容器内内存受限但主机内存充足cgroup 工具检查 cgroup 事件OOM-killed 记录调整容器内存上限或优化堆外占用注意容器场景下free显示的是宿主机的内存不是容器配额。在 pod 或容器里做内存分析时必须看 cgroup 的memory.usage_in_bytes和memory.events否则会得出完全相反的结论。很多年轻同事在容器里查内存问题命令敲下去发现free还有 20G但应用已经 OOM 重启就是因为忽略了 cgroup 限制。另外还有一个容易犯的错压测过程中开了top交互界面然后手动刷新感觉“好像没变化”就下结论说内存没问题。这种结论没有任何可靠性。要么用脚本自动采集数据要么用top -b -n 1配合循环落盘总之必须形成带时间戳的数据文件。5.2 监控工具选型的取舍完整的性能测试内存监控方案会有专门的 APM 或 GrafanaPrometheus但很多时候现场没有这套基建我们得靠系统自带命令和白话脚本。我的建议是快速巡检用free -h和vmstat 1组合。压测期间必须落盘历史数据至少用sar作为基础如果无法用sar就写一个 5 秒间隔的轮询脚本把/proc/meminfo关键字段和/proc/pid/status记录下来。精确到进程内部的定位用/proc/pid/smaps或pmap -x这些是内核提供的原始数据比从应用层推断更可靠。针对 Java 应用额外加上jstat或 GC 日志因为堆内存行为单靠 OS 指标解读不出来。工具不是越多越好关键是每次压测前要把采集脚本准备好固定好采样频率。我踩过不少回坑压测跑到一半发现sar默认没启用最后只能靠残缺日志补救。所以准备压测环境时第一条要做的就是检查监控链路是否完整而不是检查被测系统的参数配置。还有一个小技巧压测结束后把数据导出为 TSV 格式用 Python 的 matplot 简单画一下曲线。哪怕只是画 RSS 和可用内存两条线很多问题一眼就能看出来比对着数字猜强太多。我通常会把关键事件的ps快照和 GC 日志时间点对齐然后标在图上定位效率翻倍。写在最后的实操心得内存分析的难点不在于记命令而在于理解指标之间的因果关系。我自己摸索出一个小小的判断顺序分享给大家先看free的available和 swap判断系统整体是否吃紧再看vmstat的si/so和cs判断是否有回收和上下文切换压力然后看目标进程的 RSS 和历史趋势判断是不是业务自身的问题最后用/proc/pid去精确定位是哪段地址空间在异常增长。每次压测都按这个顺序走一遍基本能覆盖绝大多数内存问题。另一个我强烈建议的习惯是压测环境的内存配置必须和生产保持一致包括内核版本、内存分配策略、大页设置、cgroup 限制等因素。我在测试环境复现过多次问题最后都因为环境差异而无法复现折腾半天发现在压测机上跑的服务内存行为和生产根本不同。所以性能测试里的内存分析本质上是对环境、指标和原理的综合考验工具只是辅助。希望大家都能少走弯路把命令输出去真正读成“系统正在发生什么”的故事。