
1. 从一次CPU 打满的误判说起top 到底在告诉你什么1.1 那次我盯着 100% 的 %CPU 看了半小时某天上午一台负责日志聚合的机器被监控标红告警内容是负载超过核数的三倍。登上机器敲下top第一屏确实吓人几个进程的%CPU顶在 90 多load average的 15 分钟值一路往上爬。按常规思路问题出在计算环节于是我花了将近半小时追查那条业务代码把可疑的循环、序列化、正则全都翻了一遍结果没有任何一处能解释这么高的消耗。后来把 top 切成线程视图按H键才看清楚真正吃 CPU 的是一组名字几乎一样的线程它们全部挂在同一个数据搬运逻辑上。再往下查发现那台机器的磁盘水位已经很高写入变慢导致缓冲区反复重试线程虽然显示在 R 状态但大量时间其实耗在上下文切换和自旋等待里。换句话说top 没有骗我是我把%CPU当成了这段代码算得太多的直接证据。它实际的语义只是这个进程在过去一个采样周期里占用 CPU 时间的比例至于这段时间花在用户态还是内核态、是真的在算还是在等得看别的字段。这次经历之后我把 top 的每一个字段重新对了一遍也养成了一个固定习惯先看顶部全局那几行再看单进程的列绝不越过概览区直接对某一列数字下结论。这篇文章就是把这份逐字段的对照表摊开讲包括哪些数字可以直接用、哪些必须换算、哪些只有在特定版本下才成立。刚接触性能排查的朋友可以照着当手册已经在线上跑了几年的同行也大概率能在里面找到几个一直误解的点。1.2 top 的版面其实只有三块别被满屏数字吓退很多人第一次打开 top 的反应是信息太密。其实它的版面只有三块顶部概览区通常 5 行视版本和配置可能合并成 4 行、进程列表区一行表头加若干行数据、底部提示与输入行交互模式下才有用来接收命令。分成这三块之后理解成本会立刻降下来因为每一块的更新节奏和统计口径都不一样。概览区是全局口径来自/proc/stat、/proc/meminfo、/proc/loadavg这几个文件每次刷新重新读一遍进程列表区是逐进程采样数据来自/proc/pid/stat、/proc/pid/status然后按当前排序字段重排底部提示行则是交互层的状态告诉你当前用的排序键、过滤条件、刷新间隔。搞清楚这个来源划分有个实际好处当 top 的数字和你从别处看到的对不上时你就知道该去哪个文件里做二次确认而不是怀疑工具坏了。还有一个容易被忽略的点procps这个软件包在各大发行版上版本差异不小尤其是procps-ng3.3.10 之后新增了一批启动参数和字段老机器上压根没有。所以网上抄来的命令跑不通第一反应应该是top -V看版本而不是怀疑自己敲错了。我在不同机器之间来回切的时候经常因为一条-o参数在旧版本上不存在而愣住这个坑后面会专门说。2. 顶部五行概览每一列数字背后的口径2.1 第一行运行时长、登录用户数与三个平均负载第一行长得像top - 10:23:41 up 128 days, 3:12, 2 users, load average: 0.52, 0.61, 0.48。前半部分基本是uptime的原样输出当前时间、开机时长、当前登录会话数。真正需要用脑子读的只有最后三个数字。load average的三个值分别是 1 分钟、5 分钟、15 分钟的指数衰减滑动平均不是简单的区间算术平均。它的统计对象是处于可运行状态R加上不可中断睡眠状态D的进程数。这里有个关键点很多人不知道D 状态的进程也算进负载。所以一台跑数据库或者大量落盘的机器CPU 明明闲着load 也能到十几。我第一次遇到 load 8 而 CPU 空闲 90% 的情况时完全懵了后来才明白这是 IO 等待把它抬上去的。判断标准别死记load 超过核数就危险。比较靠谱的做法是拿 load 除以逻辑核数低于 0.7 通常很闲0.7 到 1.0 属于正常波动长期高于 1.0 说明有排队高于 2.0 就要查了。但这条经验只在 CPU 型负载下成立IO 型负载必须结合后面 CPU 行里的wa一起看。另外15 分钟值比 1 分钟值更有参考意义1 分钟值容易被一次定时任务瞬间拉高看到就慌属于自己吓自己。2.2 第二行Tasks 的五个计数与僵尸进程第二行是Tasks: 312 total, 2 running, 310 sleeping, 0 stopped, 1 zombie。这行的价值在于快速判断系统里有没有卡住的进程。running数量长期大于核数说明有排队zombie不为零要留意但也不用立刻紧张。僵尸进程的本质是子进程已经退出、父进程还没调用wait()回收它的退出状态内核保留了那条进程表项。它几乎不占内存只占一个 PID所以少量僵尸不影响性能但如果僵尸数量持续增长那才是真问题——大概率是某个父进程没有正确处理子进程退出时间长了会把 PID 空间吃掉。清理僵尸的办法不是kill -9僵尸本身它已经死了杀不动而是找到它的父进程处理掉让内核完成回收。stopped一般是被信号暂停的进程常见于调试场景或者被误操作CtrlZ挂到后台的作业。生产环境里如果莫名出现一堆 stopped检查一下是不是有脚本在非交互环境里触发了挂起。2.3 第三行CPU 时间分项的七个格子里藏着最多信息这一行是排障时看得最多、也最容易看错的地方%Cpu(s): 12.3 us, 4.1 sy, 0.0 ni, 78.6 id, 3.8 wa, 0.0 hi, 1.2 si, 0.0 st各字段的含义和排查价值我用一张表列清楚比硬背有用字段含义排查时的读法us用户态 CPU 时间占比不含 nice 调整过的高说明业务代码在算去查算法和循环sy内核态 CPU 时间占比高说明系统调用、上下文切换、网络协议栈负担重ninice 值被调高的用户进程占用一般很低异常高说明有人手动调过优先级id空闲结合其他项看别单看wa等待 IO 完成的时间高说明磁盘或网络存储慢重点查hi硬中断高常见于网卡中断密集检查中断亲和性si软中断高常见于网络包处理和定时器密集场景st被宿主机借走的时间虚拟机/容器环境专属高说明宿主资源被争抢最容易被误读的是sy和wa。sy高不一定是内核有 bug很多时候是应用做了太多小粒度系统调用或者线程数爆炸导致调度开销飙升。wa高也不一定是磁盘坏了可能只是有个大文件在做顺序写或者某个 NFS 挂载点响应变慢。我遇到过wa长期 20% 以上最后定位到是一个日志组件每写一行就fsync一次改成分批刷盘之后直接掉到 2%。st这个字段只有虚拟化环境才有意义物理机上看不到或者恒为 0。它表示宿主机的物理 CPU 分给了别的实例本该给你但没给到的时间。这个值持续超过 5%基本可以判定是宿主层面资源争抢自己在实例内部怎么优化都没用得找平台侧协调。2.4 第四、五行Mem 与 Swap 的分母陷阱这两行长这样MiB Mem : 15872.0 total, 412.3 free, 9871.6 used, 5588.1 buff/cache MiB Swap: 4096.0 total, 4096.0 free, 0.0 used. 11234.5 avail Mem第一眼很多人只看free看到只剩几百兆就开始紧张这是最经典的误解。Linux 的设计思路是空闲内存是浪费所以它会主动把没用到的内存拿去做页缓存buff/cache一旦应用需要这部分会立刻被回收。真正应该看的是avail Mem它表示在不触发交换的前提下还能分配给新进程的内存估算值。这个值健康free再小也没事。used这个字段在不同procps版本里口径有细微差异老版本是total - free - buffers - cached新版本对buff/cache的合并方式做过调整所以同一台机器用不同版本的 top 看used可能差出几百兆属于正常现象不用纠结。Swap 那行要结合si/so看但 top 概览里不直接给这两个值需要交互模式下按f把SWAP相关字段调出来或者干脆用vmstat 1看。Swap 占用高本身不致命持续有 swap in/out 才致命因为那意味着内存真的不够了每次换页都是毫秒级的停顿。云主机上很多镜像默认不开 swap这种情况下avail Mem逼近 0 就要小心 OOM。3. 进程列表区哪些列能直接用哪些必须换算3.1 身份与优先级PID、USER、PR、NI 这四列怎么读PID是进程号USER是有效用户名。这两个没什么歧义但有个细节top 默认显示的是有效用户而不是真实用户su或者 setuid 程序切换身份后看到的 USER 可能和你预期不一致。PR是优先级普通进程的值等于20 NI所以默认 NI0 时 PR 显示 20。这个数字越小优先级越高内核调度器会更倾向把 CPU 分给它。如果看到PR列显示rt说明这是实时调度策略的进程它的优先级压过所有普通进程一旦这种进程进入死循环能把整台机器拖死。我曾经见过一台机器 load 冲到 40 但us只有 15%最后发现是某个实时优先级的采集程序异常。NI是 nice 值范围 -20 到 19负值表示更贪心。调整它用renice命令交互模式下按r直接对选中的 PID 操作。这里有个经验不要随手把业务进程 nice 调成 -20因为你以为在优先保障它实际上是让它在争抢时碾压同机器上的其他进程包括监控代理和日志采集最后出问题时你连现场数据都拿不到。3.2 VIRT、RES、SHR三个内存数字别再混着看这三列是误解重灾区我按它到底统计了什么来说明VIRT是进程申请的虚拟地址空间总量。它包括已经分配但还没真正使用的内存、映射的文件、共享库甚至包括mmap出来但没碰过的页面。所以看到一个 Java 进程 VIRT 显示 30G 完全正常不代表它真占了 30G 物理内存虚拟地址空间在 64 位系统上几乎是无限的。RES是常驻内存也就是当前真的落在物理内存里的部分。这是最接近实际占用的数字但它有两个陷阱一是不包含被换出到 swap 的部分二是共享内存会被重复计数比如 libc 被 50 个进程映射每个进程的 RES 里都会算一份。所以你把所有进程的 RES 加起来结果远超总物理内存这不是 bug是统计方式决定的。SHR是 RES 中可被共享的部分包括共享库和tmpfs上的内容。判断一个进程是不是在独吞内存粗略算法是RES - SHR。这个差值才是它比较独占的部分。列名统计口径常见误读VIRT虚拟地址空间总量当成实际物理占用RES已驻留物理内存含共享各进程相加当作总占用SHRRES 中可共享的部分忽略它导致重复计算%MEMRES 占物理内存总量的百分比分母不是 avail Mem别搞混%MEM的分母是MemTotal不是avail Mem这点必须记牢。一台 16G 的机器上某个进程%MEM显示 60%说明它 RES 大约 9.6G而不是吃掉了可用内存的 60%。3.3 %CPU 能超过 100TIME 的单位是百分之一秒%CPU是相对单个逻辑核的百分比不是相对整机。这就是为什么在一台 32 核机器上你可能看到某个进程显示%CPU 2500意思是它同时跑满了 25 个核而不是数据错了。同理top概览区的%Cpu(s)是所有核加起来的归一化结果两者口径完全不同放在一起比较是没有意义的。还有个关键细节%CPU是基于两次采样之间的差值算出来的瞬时值。这就带来一个非常实用的结论——top -b -n 1只采一次输出的第一屏%CPU往往是不可靠的因为程序刚启动时没有上一次采样作为基准某些版本会退化成用进程生命周期内的平均值。要做监控采样正确姿势是采两次以上取后面几次的值top -b -n 2 -d 1 -o %CPU | awk /^ *[0-9] /{print} | tail -20TIME是进程累计消耗的 CPU 时间单位是百分之一秒格式为分:秒.百分秒比如123:45.67表示 123 分 45.67 秒。这个字段的价值在于判断持续性消耗%CPU是瞬时的可能被一次波动骗过去而TIME一路增长说明这个进程从头到尾都在吃 CPU值得深挖。3.4 S 状态位速查D 状态是很多诡异问题的答案S列的取值不多但每个都有实际排查意义R正在运行或在运行队列里等待S可中断睡眠最常见等事件D不可中断睡眠通常在等 IO不能被信号杀死T停止状态t被调试器跟踪Z僵尸I空闲的内核线程重点说D。处于D状态的进程你kill -9是没用的它要等内核里的那次 IO 完成才会响应信号。我在一次排查中看到十几个进程堆在Dwa高达 40%最后定位到是一个挂载的网络存储响应超时。这种情况下杀掉进程解决不了问题得从存储侧下手。top 默认不会高亮显示D状态要做统计可以用psps -eo state,pid,comm | awk $1D把这条命令做成定时采集配合 top 的wa一起看能快速区分CPU 型卡顿和IO 型卡顿。4. 把 top 调成趁手的仪表盘交互键与启动参数4.1 必记的那十几个交互按键top 的交互能力比默认界面强得多但大多数人只会按q退出。常用的几组我按场景归一下排序类P按 CPU、M按内存、T按累计时间这三个是默认就能用的。f进入字段管理界面用方向键选择、按s设为排序键、空格键开关显示这个界面看起来古老但功能完整能把你不关心的列全部隐藏屏幕立刻清爽。视图类c切换显示完整命令行默认只显示进程名排查时这个键非常关键、H切换线程视图、1展开每个逻辑核的独立使用率、i隐藏空闲进程、u按用户过滤、o按任意字段过滤比如o然后输入%CPU10。操作类k向进程发信号、r改 nice 值、s改刷新间隔、n改显示行数。注意k默认发的是SIGTERM要发别的信号需要手动输入编号。显示类z开启彩色、b高亮当前排序列、x把排序列加粗、E/e切换内存单位KiB、MiB、GiB。内存单位这组特别值得改默认 KiB 显示一串长数字眼睛根本没法快速判断量级。改完这些配置后按W会把当前设置写进~/.toprc下次启动自动生效。这个键我强烈建议在每台长期维护的机器上都按一次比每次启动手动调省事太多。4.2 非交互环境下的采样-b的用法与几个坑登不上机器、只能看日志或者要从监控系统里拉数据的时候-b批处理模式是唯一选择# 每秒采一次共五次输出到文件 top -b -n 5 -d 1 /tmp/top_sample.log # 只看指定进程 top -b -n 2 -d 1 -p 1234,5678 # 看某个进程的线程分布 top -b -H -n 2 -d 1 -p 1234 # 按内存排序取前十procps-ng 3.3.10 及以上 top -b -n 2 -d 1 -o %MEM | grep -A 10 PID这里有几个我踩过的坑。第一个是-d的最小值某些版本限制不能小于 1 秒你要做高频采样得配合-b和交互模式的区别来绕或者直接用pidstat、sar这类工具没必要死磕 top。第二个是首屏不可靠前面说过-n 1的%CPU参考价值很低至少采两次。第三个是-o的版本依赖老版本 top 没有这个参数会直接报错退出脚本里加个版本判断或者用sort后处理更稳妥top -b -n 2 -d 1 | awk NR7 | sort -k9 -nr | head -10第四个是输出宽度批处理模式下默认按 80 列截断命令行长一点就看不全了加-w 512同样有版本要求或者设置COLUMNS环境变量能解决。4.3 字段定制的两种思路交互式与配置文件字段定制有两条路。一条是前面说的按f进界面选然后W保存另一条是直接改~/.toprc。文件里每个字段对应一个开关位格式在不同版本间有差异手改容易出错所以我一般还是走交互界面。需要给多台机器统一配置时可以把一份调好的.toprc批量分发。注意这个文件会记录排序键和刷新间隔如果分发的是按内存排序的配置运维同事上来看 CPU 问题会发现排序不对所以团队内最好约定一套默认配置把常用的几列固定下来排序键保持 CPU。还有个小技巧top -c -o %CPU -p $(pgrep -d, 进程名)可以把某个服务的所有进程一次性聚合到一个视图里比一个个查 PID 高效得多。这条命令我在处理多实例部署的服务时用得最多。5. 三类高频误判与交叉验证手法5.1 负载高而 CPU 闲把 wa 和 D 状态一起拉出来这是最典型的一类误判。load 飙到 20但%Cpu(s)里id还有 70%us加sy加起来不到 20%。这时候别再纠结 CPU 了问题几乎肯定在 IO。判断链路是这样的先在 top 里看wa如果wa也高基本确认如果wa不高但 load 依然高那就用ps数一下D状态进程有多少。wa统计的是 CPU 等待 IO 的时间但如果 IO 是由内核线程或者异步方式发起的CPU 可能根本没在等而进程仍然卡在D上这种情况下wa会偏低容易被漏掉。确认是 IO 问题之后top 能提供的信息就到头了得换工具iostat -x 1看每块盘的%util和awaitiotop看具体是哪个进程在读写。我一般的顺序是 top → iostat → iotop三步之内基本能锁定。顺带说一句await高但%util不高常见于网络存储或者虚拟化磁盘因为队列层面的统计和物理设备的统计口径不一样。这种环境下判断磁盘慢不能只看 util。5.2 内存看起来吃满buff/cache 与 avail 的正确读法前面提过一次这里展开说。free很小、used很大、buff/cache很大这是 Linux 的正常状态不是内存泄漏。判断是否真的内存紧张看三个东西avail Mem是否还充足、swap 是否有持续的 in/out、有没有 OOM 记录。# 看内核有没有杀过进程 dmesg -T | grep -i out of memory # 看 swap 活动 vmstat 1 5vmstat输出的si和so两列如果有持续非零值才说明内存真的不够在换页。偶尔几个非零值不用管内核有时候会主动把冷页面换出去。第二种误判是某个进程内存一直涨。这时候要看增长的是RES还是VIRT。VIRT涨而RES不动通常是内存分配器保留了地址空间没释放属于正常行为RES持续涨才是真占用了物理内存。Java 这类有自己堆管理的运行时还要区分堆内和堆外RES涨可能是元空间、直接内存缓冲或者线程栈导致的光看 top 定位不到得上jcmd、pmap这类工具。5.3 容器环境里 top 数字的失真与绕行方案这个坑值得单独说清楚。在容器里跑 top默认读的是宿主机的/proc所以你看到的MemTotal是宿主机的总内存、CPU 核数是宿主机的核数、进程列表里甚至能看到别的容器的进程取决于命名空间。结果就是容器明明被限制在 2 核 4Gtop 里显示 64 核 256G用它算出来的所有比例都是错的。绕行有两种思路。一种是直接读 cgroup 的限制值cat /sys/fs/cgroup/memory.max # cgroup v2 的内存上限 cat /sys/fs/cgroup/cpu.max # cgroup v2 的 CPU 配额 cat /sys/fs/cgroup/memory.current # 当前用量cgroup v1 的路径是/sys/fs/cgroup/memory/memory.limit_in_bytes这一套字段名不一样得先确认系统用的是哪个版本。另一种是让运行时自己处理部分容器方案会挂载经过适配的/proc让 top 读到隔离后的数字但这需要平台侧支持不是自己能改的。在容器里做性能分析我的习惯是先确认口径再下结论top 的数字用来做相对比较哪个进程比别的进程高、cgroup 的数字用来做绝对判断有没有超限、离上限还有多少。两套数据混着用结论一定出错。5.4 top 之外还得看谁交叉验证的固定组合top 是入口不是终点。我常用的组合是这样top定方向、vmstat 1看整体趋势、pidstat -u -d -r 1看单进程的 CPU/IO/内存三项、iostat -x 1看设备层、ss -s或者netstat -s看网络异常统计。这套下来八成以上的性能问题能定位到具体层次。举个例子top 里某个进程%CPU高us也高看起来是业务代码问题。但用pidstat -u -t 1 -p pid看线程级数据可能发现是某个异步任务线程在空转再配合perf top -p pid看热点函数才能真正落到代码行。没有这几层下钻光靠 top 只能得出这个进程忙这种没有行动价值的结论。最后分享一个我在实际使用中养成的习惯遇到任何性能问题第一件事是把top -b -n 3 -d 1的完整输出存一份文件名带上时间戳。原因很实际——很多问题在你排查的过程中就自愈了或者被你自己的操作改变了现场事后想回溯当时 %CPU 到底是多少、wa 有多高就来不及了。这份快照成本极低但在复盘和写报告的时候能救命。