ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux内存检测工具全解析:从free到valgrind的排查实战

Linux内存检测工具全解析:从free到valgrind的排查实战 先亮个观点Linux下做内存性能检测最怕的不是工具少而是工具太多、指标太杂新手一看free、top、vmstat、smem、pmap、valgrind铺天盖地涌过来直接懵圈。我早期做运维时也走过这条路把每个命令的man page啃了一遍真到线上内存飙高的时候反而不知道该先看哪个。后来踩的坑多了才慢慢总结出一套自己的排查套路。这篇文章我就以实际运维和性能调优的视角把Linux内存检测工具从入门到进阶捋一遍。不光告诉你每个工具怎么用更重要的是告诉你什么场景该用哪个、指标怎么解读、常见的坑在哪。无论你是刚接触Linux的运维小白还是已经被内存问题折腾过几轮的开发、测试同学这篇都值得花十分钟看完至少能帮你省下几个小时的查资料时间。1. 先摸清家底free、top、htop 这三板斧1.1 free命令的输出你真的看懂了吗说到内存检测free是第一个要上手的命令。但很多人对free的输出理解停留在表面特别是看见available和free两个数值时容易糊涂。我见过不少同事拿着free的输出跑来问明明free列还剩几百MB为什么监控报警说内存不足这就要说清楚free命令里几个关键列的含义了。执行free -h典型的输出长这样total used free shared buff/cache available Mem: 31G 18G 1.2G 245M 12G 12G Swap: 8.0G 2.1G 5.9Gtotal是物理内存总量used是已使用的内存free是完全没有被占用的内存buff/cache是内核用来做磁盘缓存和文件缓存的部分available才是应用程序真正可以使用的内存。内核在内存紧张时会自动回收buff/cache来满足新的内存申请所以available才是判断内存是否够用的核心指标。举个例子如果一台机器free只有500MB但buff/cache占了20GBavailable显示还有15GB这种情况下系统并不会因为内存不足而触发OOM因为内核可以快速回收缓存。反过来如果available持续很低比如不到总内存的10%同时swap使用率在上升那才是真正的内存瓶颈。实操中我习惯加-s参数做持续观察比如free -h -s 5每5秒刷新一次配合内存监控告警能快速判断内存是在持续增长还是偶发波动。1.2 top和htop别只盯着那一串数字top命令是排查内存问题时用的最多的工具但很多人只是看一眼RES和%MEM就完事了。top输出的关键列包括VIRT、RES、SHR、%MEM这几个值的含义完全不同。VIRT进程虚拟内存大小包含了进程可能使用的所有地址空间包括还没实际分配的、映射到磁盘的所以这个值通常会很大参考意义不大。RES进程实际驻留在物理内存中的部分这才是真正占用的物理内存。SHR共享内存大小包括共享库等这部分可以被多个进程共用。%MEMRES占总物理内存的百分比。我遇到最多的误判场景是某个java进程VIRT值显示几十GB直接把新同事吓一跳以为内存泄漏了。实际上是因为JVM进程启动时预留了大块虚拟地址空间并没有真正占用物理内存RES才能反映真实情况。top还有一个交互模式值得掌握进入top界面后按M键可以直接按内存占用排序瞬间找出最吃内存的进程。按P键则按CPU排序这两个快捷键排查问题效率极高。htop是top的增强版支持彩色显示、树状进程视图、鼠标操作还能直接在界面里搜索进程名。虽然htop默认在很多发行版上没安装但一条apt install htop或yum install htop就能搞定。我个人更喜欢htop的一点是它可以直接显示每个CPU核心的使用率排查多核负载不均时非常直观。2. 深入进程层面smem、pmap、pidstat 的配合使用2.1 smem算清楚真实内存占用的利器top里的RES看起来直观但有个问题共享内存会被重复计算。比如两个进程同时加载同一个共享库这部分的物理内存在RES里两个进程各算了一遍加总起来会虚高。smem就是为解决这个问题而生的它统计时考虑了共享内存的归属输出USS、PSS、RSS三组指标USSUnique Set Size进程独占的物理内存不包括任何共享部分。PSSProportional Set Size按共享比例分摊后的物理内存比如一块共享内存被3个进程共用每个进程算三分之一。RSSResident Set Size和top的RES基本一致。实际排查中判断单个进程真实内存占用应该以PSS为主USS和RSS作为辅助参考。特别是容器环境下多个容器共享宿主机内核和公共库用RSS做配额评估偏差会很大。smem的安装也比较简单Debian/Ubuntu系直接用apt install smem即可。常用参数包括smem -k -s pss # 按PSS排序并以KB/MB单位显示 smem -k -t -p # 显示百分比并输出总计行 smem -u -k # 按用户汇总内存占用我统计一个容器平台上各应用实际内存占用时就是用smem -k -s pss来拉取数据比直接看top的RES准确很多。2.2 pmap和pidstat定位进程内部的内存分布找到最吃内存的进程后下一步就是搞清楚这个进程的内存到底用在哪。pmap命令可以显示进程地址空间的详细映射是定位内存问题的主力工具。用法很简单拿到进程PID后执行pmap -x 12345输出会列出进程的每一块内存映射区域包括代码段、数据段、堆、栈、共享库、匿名映射等以及各自的RSS和脏页大小。重点看堆heap和匿名映射anon部分这两个区域通常占了大头。如果发现某个匿名映射区域特别大而且持续增长基本可以怀疑有内存泄漏。pidstat是sysstat包里的工具主要用来做进程维度的性能监控。排查内存时我最常用的是pidstat -r 5 # 每5秒输出一次各进程的内存使用情况它会显示进程的RSS和%MEM配合时间戳可以清晰看到内存是否随时间线性增长。如果某个进程的内存每5秒都涨几MB而且一直没有回落那基本可以判定有问题。2.3 /proc目录Linux内存信息的源头讲了这么多工具其实所有命令的数据都来源于/proc虚拟文件系统。掌握直接读取/proc的方法在工具不可用或需要更底层数据时会非常有用。关键文件包括/proc/meminfo系统内存的整体使用情况free命令的数据源。/proc/pid/status进程级内存信息VmLck、VmRSS、VmSwap、VmPeak等字段非常有用。VmPeak显示进程历史内存峰值如果当前值和峰值差距过大说明进程曾申请过大量内存但已释放。/proc/pid/smapspmap的数据源比pmap更详细可以精确查看每一段映射的RSS、PSS、共享页和私有页数量。/proc/pressure/memoryPSIPressure Stall Information数据反映内存压力导致的进程停顿时间这是判断内存是否成为系统瓶颈的高级指标。举个实际例子我用下面这条命令快速查看某个进程的内存详情grep -E VmRSS|VmSwap|VmPeak /proc/12345/status如果VmSwap数值很大说明进程有大量内存被换到了swap分区这通常是物理内存不足的强烈信号。需要说明的是swap的使用在高内存压力场景下不一定是坏事但如果持续进行swap换入换出系统性能会明显下降这种抖动比单纯内存占用高更危险。3. 内存泄漏排查实战从工具到方法论3.1 面对象内存泄漏的工具链排查内存泄漏工具选择要看问题的语言环境。C/C程序我首选valgrindJava程序用jmap配合visualvmPython程序用tracemalloc方法论是相通的——先判断是否泄漏再定位泄漏点。valgrind的massif工具可以堆内存的使用做采样分析。用法很简单valgrind --toolmassif ./your_program运行结束后会生成massif.out. 文件然后用ms_print查看分析结果ms_print massif.out.12345输出会以图表和表格形式展示堆内存随时间的变化通过观察曲线走势能直观判断内存是平稳还是有持续增长趋势。valgrind还有个memcheck工具专门用来检测内存访问错误包括越界读写、使用未初始化内存、重复free等问题这些往往是内存异常的根源。不过valgrind对性能影响很大程序运行速度可能慢10到50倍所以不适合在线上的高负载环境跑一般都是在测试环境复现问题后再用。3.2 Java进程别被JVM堆骗了Java进程的内存排查和原生程序不太一样因为JVM把内存分成了堆内和堆外两部分。JVM堆内内存由JVM的垃圾回收机制管理可以通过jmap命令查看堆的使用情况jmap -heap pid # 查看堆参数和当前使用情况 jmap -histo pid # 查看堆中对象的统计信息按对象数量排序其中jmap -histo输出的前几行通常是排查内存泄漏的关键。如果看到某些业务对象比如查询结果集、Session对象、消息队列消息数量异常大而且每次执行gc后数量不下降基本可以断定堆内存泄漏了。但是Java进程占用的物理内存RES往往远大于JVM堆大小这是因为还有元空间Metaspace、线程栈、JIT编译器缓存、DirectByteBuffer等堆外内存。特别是用了Netty这类高性能网络框架的应用DirectByteBuffer会大量分配堆外内存这块如果不留意容易落入“我的JVM堆明明只有2GB但整个进程占了6GB”的困惑。排查堆外内存可以使用jcmd pid VM.native_memory # 需要启动时加-XX:NativeMemoryTrackingsummary参数 pmap -x pid | grep anon # 查看匿名映射区域大小另外suid_dumpable和/var/log/messages里的OOM记录也值得关注。我曾经遇到过一个情况JVM堆一切正常但进程RSS持续增长最后定位到是网络框架的堆外内存池没有及时释放这类问题用传统堆转储是查不出来的。3.3 通用排查流程三条判断准则无论什么语言我总结了一套通用的内存泄漏排查准则第一观察内存增长模式。内存持续单调递增且增速基本稳定大概率是泄漏。内存上升到某个区间后周期性回落更可能是业务峰值或对象增多。内存锯齿状波动则要关注GC是否频繁。第二对比多个维度的数据。系统总内存、进程RSS、堆内内存三个数据一起看。如果系统总内存和进程RSS都在涨说明进程确实在向操作系统申请更多内存。如果进程RSS涨但堆内内存稳定优先怀疑堆外内存。如果RSS稳定但系统可用内存下降要考虑是不是页缓存等系统级内存占用。第三利用监控和压测复现。我会在压测环境下用固定请求量跑30分钟以上每隔1分钟记录一次内存数据形成趋势图。如果斜率明显大于0就可以进入定位阶段。4. 整机内存异常的系统级排查与调优4.1 swap异常和OOM killer怎么应对系统级内存问题最典型的表现是swap使用率飙升和OOM killer频繁触发。swap分区的使用本身不是灾难但swap换入换出持续发生说明物理内存已经严重不足。这里我要提一个很多人忽略的点swap其实有“预热”机制也就是内存不紧张时也会有一些冷页面被换入swap这是正常的。真正需要警惕的是持续的siswap in和soswap out数值。用vmstat可以观察vmstat 2输出中si和so两列如果持续非零说明系统内存压力很大进程在内存和swap之间频繁搬运数据。这种场景下加物理内存、缩减进程内存占用、调整swappiness参数是三个主要方向。swappiness参数控制内核更倾向于使用swap还是回收页缓存。一般默认值是60对于数据库服务器、缓存服务器这类对延迟敏感的场景可以适当调低sysctl vm.swappiness10 echo vm.swappiness10 /etc/sysctl.confOOM killer是内核在内存耗尽时的最后手段。排查OOM问题第一件事是看系统日志dmesg | grep -i oom journalctl -k | grep -i oom日志中会明确记录是哪个进程被杀以及当时的内存状况。如果某个进程频繁被OOM killer干掉可以从几个方向下手检查进程是否真的有内存泄漏、调整进程的cgroup内存限制、优化应用程序的内存使用模式。对于关键业务进程适当降低其OOM score可以避免被优先杀掉但这不是长久之计根治内存问题才是正路。4.2 内存性能的采样与压测工具除了问题排查主动的内存性能评估也是运维和研发的常规工作。我的习惯是用sysbench和stress来做内存基准测试和压力测试。sysbench做内存基准测试的典型命令sysbench memory --threads8 --memory-total-size10G run这个命令会测试8线程下分配和读写10GB内存的吞吐量和延迟。通过对比不同机器、不同内存配置下的测试结果可以大致评估内存带宽是否达标。stress工具更适合做系统压力测试stress --vm 4 --vm-bytes 2G --timeout 60s上面命令会生成4个vm worker每个分配2GB内存持续60秒。这适合用来测试系统在内存压力下的表现比如验证OOM killer是否正常、观察可用内存告警策略是否合理、测试应用在内存不足时的降级表现。还有一点值得提perf工具可以用来分析内存访问相关的性能事件。比如perf stat -e cache-misses,cache-references,page-faults -p 12345这个命令能统计进程的缓存命中率、缓存引用次数和缺页次数。缺页次数异常高说明程序的内存局部性不好频繁访问不连续的内存区域这会显著拖慢性能。这类问题在用传统内存工具查不出异常但应用又感觉明显的场景下特别有效。4.3 容器环境下的特殊视角现在的Linux服务器很多内存问题都发生在容器环境下。容器内存排查和传统物理机最大的区别在于层级变多了容器内有cgroup限制、宿主机有全局内存水位、还有Page Cache的归属问题要理清。在容器里看内存不能只看free和top还要关注cgroup层面的数据。cat /sys/fs/cgroup/memory/memory.usage_in_bytes cat /sys/fs/cgroup/memory/memory.limit_in_bytes cat /sys/fs/cgroup/memory/memory.statmemory.usage_in_bytes是当前实际使用量memory.limit_in_bytes是cgroup设定的上限当usage逼近limit时会出现容器内OOM但宿主机本身可能完全正常。这就是为什么有时候宿主机的free显示内存充足但容器应用却反复崩溃。还有一个坑是Page Cache的归属。在容器内执行free看到的buff/cache和宿主机看到的不是一个视角如果有大量文件读写业务容器内的cache可能占用大量内存而内核回收cache往往滞后。这类问题的排查思路跳出了进程和容器层面要回到宿主机上使用echo 3 /proc/sys/vm/drop_caches手动清理缓存验证但需要注意的是drop_caches只是清回收可回收部分对正在使用的内存无效。5. 一套完整的排查案例从free到定位问题5.1 现场现象和初步判断有一次线上应用反馈服务响应变慢请求耗时从几十毫秒飙升到几秒。我登录服务器后第一件事是看free -h输出显示total used free shared buff/cache available Mem: 31G 29G 128M 1.2G 1.8G 1.1Gavailable只剩1.1G而且buff/cache也非常低说明系统已经处于内存紧张状态内核连可回收的缓存都不多了。再执行vmstat 2看到si和so列持续有数值交换活动非常频繁。基本可以判断物理内存不足系统在靠swap硬撑。5.2 定位内存消耗大户第二步执行top按M键排序看到java进程PID 23456的RES占到了18G而服务器的总内存才31G这个进程显然是内存消耗的主力。问题来了一台服务器上只有这个java进程内存高还是其他进程也都高看了top前10行的输出后发现除了这个java进程其他进程的RES都在几百MB以内说明问题基本集中在这个java进程上。接着用jmap -heap 23456查看JVM堆发现堆的最大值是8G已用5GGC频率正常堆内内存并没有明显的异常。但进程RES却高达18G堆外内存占了10G左右这明显不寻常。5.3 深挖堆外内存并找到根源用pmap -x 23456输出后把匿名映射的内存加起来重点看有没有特别大的单个映射段。经过比对发现一个大小大约6G的匿名映射块地址范围非常固定持续增长。再结合jcmd 23456 VM.native_memory查看本地内存追踪结果定位到是DirectByteBuffer相关的内存占用。回到应用层面看代码发现有一个批量处理模块循环里通过ByteBuffer.allocateDirect()创建缓冲区但没有主动释放。由于DirectByteBuffer的回收依赖GC和Cleaner机制在高吞吐场景下这些堆外内存块堆积了起来迟迟没有被回收。问题定位后排除了处理方式不复杂在代码中显式使用完DirectByteBuffer后调用cleaner机制释放或者改为复用缓冲池避免频繁创建大块堆外内存。优化后同样压力下进程RES回落到9G以内系统可用内存恢复正常服务延迟也稳定了下来。5.4 这个案例复盘出来的排查思路回头总结这次排查整个过程其实是标准套路第一先用free和vmstat判断整机内存水位区分是系统资源不足还是局部进程异常。第二用top和ps找到具体进程缩小排查范围。第三结合进程技术栈选择工具Java进程就jmap、jcmdC进程就pmap、valgrind。第四定位到具体的内存分配点修复后压测验证。这套流程走下来从发现问题到定位根因大概用了40分钟。如果一开始就埋头去看系统日志或者抓包往往事倍功半。6. 常见误区与高频问题速查6.1 新手经常踩的5个坑这些坑是我这些年见到的最高频问题列出来给大家提个醒。第一个坑是把VIRT当成真实内存。很多新手看top看到VIRT列数值巨大就断定内存泄漏实际是虚拟内存的地址空间预留不代表真的占用了物理内存。判断真实占用一定要看RES和PSS。第二个坑是忽略buff/cache的回收机制。看到free命令里free列很少就以为内存不足没有关注available字段。判断内存是否充足的准确尺子是available不是free。第三个坑是动态内存分析时不带时间维度。只看一次快照无法判断内存趋势一定要持续性地采样。我习惯用pidstat -r 5或者写个脚本每隔几秒记录一次保留30分钟以上的趋势数据再分析。第四个坑是Java进程只查堆内内存。JVM进程的物理内存还包括元空间、线程栈、代码缓存、堆外缓冲区只用jmap -heap很可能漏掉真凶。第五个坑是直接在生产环境跑valgrind。valgrind的慢会让生产应用直接超时正确做法是压测环境复现、隔离分析。如果非要在生产环境查C内存泄漏可以用jemalloc的profiling功能或gdb attach采样影响相对小一些。6.2 高频排查问题速查表场景现象第一优先命令关键解读系统整体内存紧张available持续偏低swap增高free -h; vmstat 2关注availablesi/so是否持续非零单进程内存异常高某个进程RES占了大半内存top -o %MEM; pmap -x先用top排序找到PID再用pmap看内部明细怀疑内存泄漏进程RSS随时间持续增长不回落pidstat -r 5 持续观察趋势连续采样至少30分钟看趋势斜率Java进程物理内存过高RES远大于JVM堆大小jmap -heap; jcmd VM.native_memory区分堆内和堆外重点查DirectByteBufferC/C程序崩溃出现段错误或非法访问valgrind --toolmemcheck在测试环境复现后使用注意性能开销容器内OOM容器被杀宿主机内存正常cat /sys/fs/cgroup/memory/memory.*看cgroup限制是否被触顶内存带宽性能差业务延迟高内存密集型任务慢sysbench memory run; perf stat对比基准值查cache miss和缺页次数这张表不是我拍脑袋写出来的全是实际操作中验证过的最短路径。按表格顺序做大部分内存问题能在20分钟内定位到具体方向。7. 如何构建自己的内存监测体系7.1 从命令行到自动化监控光靠手动敲命令去排查问题只能解决当下故障想要提前发现风险必须建立持续的内存监控体系。我的建议是监控分三层做主机层、进程层、应用层。主机层就是用Prometheus的node_exporter采集内存指标包括MemTotal、MemAvailable、MemFree、Buffers、Cached、SwapUsage等设置可用内存低于20%即告警的规则。进程层采集关键进程的RES和PSS这个可以通过process_exporter或者cAdvisor实现重点监控内存增速和峰值。应用层则是每个应用自己暴露的内存指标比如Java的JMX可以通过jmx_exporter接入Prometheus。告警规则也有讲究我之前踩过的坑是只设置单点的绝对值告警导致半夜经常被噪音吵醒。后来改成了更合理的组合式规则可用内存低于20%且持续5分钟或者进程内存增速超过100MB/小时且持续30分钟这种条件组合能大幅降低误报率。7.2 常用的几个内存分析小脚本有些场景下我需要快速从一堆进程中找出内存增长最快的一般会写出如下的小命令组合# 按PSS排序输出当前各进程内存占用Top10 smem -k -s pss | head -20 # 每5秒采样一次某进程RSS保存成日志 for i in $(seq 1 60); do date %s; grep VmRSS /proc/pid/status | awk {print $2}; sleep 5; done memory_trend.log # 查看当前系统剩余内存和Swap使用情况 while true; do free -m | awk NR2{print strftime(%H:%M:%S), total:$2, used:$3, free:$4, available:$7} NR3{print swap_total:$2, swap_used:$3}; sleep 10; done这些命令虽然简陋但胜在灵活ssh到任何一台机器上都能直接跑不用装额外工具。特别是第一个查看VmRSS的小循环我在地排查疑似泄漏时总用它来落一份趋势数据比人工盯屏幕靠谱得多。7.3 内存性能调优的优先级判断内存问题的处理优先级我的经验是先保稳定再求性能。系统性内存不足的场景先确保服务不OOM方法包括加内存条、调整应用内存参数、减少同时运行的服务数量、合理设置swap。稳定之后再考虑性能层面的优化比如调整swappiness参数、优化应用内存分配模型、使用内存池减少频繁分配回收带来的性能损耗。有一类问题容易被忽略内存够用但内存分配效率低。典型表现是程序频繁调用malloc创建小块内存导致堆碎片化和系统调用开销大CPU的sys占用率明显偏高。这种情况用top看CPU会发现%sy比%us还高用strace频繁看到brk或mmap的系统调用。针对这个场景在C/C里引入jemalloc或tcmalloc往往能立竿见影地降低内存分配开销。这个判断方法很简单如果系统的内存总量明明够用但CPU的sys占用持续偏高就去看是不是内存分配太频繁了。内存分配本身也是CPU密集操作被忽视的频率其实很高。写在最后Linux内存性能检测这件事工具本身并不复杂真正难的是建立一套“从现象到根因”的排查思路。我这些年吃过不少亏最深的体会有三点一是判断内存够不够用永远看available而不是free二是单看一次快照无法定位内存泄漏必须拉长时间维度观察趋势三是不同层面的问题用不同的工具组合别指望一个工具解决所有问题。文章里提到的这些命令和排查思路都可以直接拿去做实验。我建议你在自己的测试服务器上故意制造一些内存压力比如写一个持续分配内存的小脚本然后用这套方法一步步定位把每个工具的输出都跑一遍这样印象会深刻很多。工具是死的排查的思路是通过一次次实战长在脑子里的。
返回列表