重新认识 top:重新读懂每一个数字
接口变慢、服务器卡顿,或者监控提示 CPU 使用率过高,很多工程师登录机器后的第一个命令都是 top。它能很快显示系统状态,但读懂这些数字,需要先知道它们统计了什么。
同样是 CPU 百分比,汇总区和进程区的计算基准可能不同;load average 计入了部分等待中的任务;内存的 free 也只表示完全空闲的部分。这些差异会直接影响判断:进程的 %CPU 超过 100% 可以正常,load 很高时 CPU 仍可能空闲,free 很小时系统也未必缺内存。
本文以 Linux 上常见的 procps-ng top 为例,按屏幕的阅读顺序,解释主要字段的含义和排查时的使用方法。
一、top 的数字从哪里来
top 的主要统计数据来自 /proc。这是内核提供的虚拟文件系统,其中的状态文件由内核在读取时提供内容,不是保存在磁盘上的普通日志。全局 CPU 时间来自 /proc/stat,内存信息来自 /proc/meminfo,进程信息则来自 /proc/[pid]/ 下的文件。

top 显示的 CPU 使用率,是两次刷新之间的平均值。例如,一个进程在一秒内用满一个逻辑 CPU 半秒,剩下半秒空闲,使用率就是 50%。因此,看到 50%,也可能意味着这段时间里曾短暂忙满。
刷新间隔越长,短暂的峰值越容易被平均掉。排查时,可以运行 top -d 1,每秒刷新一次,连续观察几次,看看使用率是持续偏高,还是偶尔上升。
运行 top 后按 h,可以在帮助页面查看当前刷新间隔。
运行中也可以按 d 或 s,输入 1 后回车,将刷新间隔改为一秒。调整好设置后,按大写 W 保存到配置文件,下次启动时沿用。
CPU 使用率需要比较一段时间内的变化,内存占用和进程状态则主要反映读取时的情况。
二、先看资源,再看进程

常见布局中,上半区显示负载、任务数量、CPU、内存和交换空间,下半区列出进程。
排障时,先用上半区判断需要关注哪类资源,再到下半区查找相关进程,这样比直接盯着排序第一的进程更有依据。
汇总区能提供方向,但不能单独确定根因。例如,内存可用量下降时,可以先查看哪些进程的 RES 在增长;如果进程内存没有明显变化,还要继续检查页缓存、内核内存等部分。
三、上半区:负载、任务、CPU 和内存
1. load average:先确认任务在等什么
第一行通常包含当前时间、系统已运行时长、登录会话数,以及 1、5、15 分钟的 load average。
Linux 的 load average 统计正在运行、等待 CPU,以及处于不可中断睡眠的任务数量,并采用指数加权移动平均(EWMA)计算负载。

内核大约每隔 5 秒更新一次负载:保留一部分旧负载,加入一部分当前任务数。这个更新周期与 top 的刷新间隔相互独立。
可以把 load 看成一块大约每 5 秒更新读数的仪表盘,
top的刷新间隔决定你多久看一眼。

1、5、15 分钟三档 load average 使用不同的权重系数 α;同一档的 α 保持固定:
| load average | τ 值(秒) | α 近似值 |
|---|---|---|
| 1 分钟 | 60 | 0.9200 |
| 5 分钟 | 300 | 0.9835 |
| 15 分钟 | 900 | 0.9945 |
Linux 内核使用预先定义的定点数常量来近似这些系数。每次更新时,变化的是旧负载和任务数 N,α 不随负载变化。任务数突然增加时,load 会逐步升高;任务数减少后,它也会逐步回落。时间常数越大,变化越慢。

上述计算可以用下面的公式表示,其中 Δt 是更新间隔,τ 是对应档位的时间常数,N 是本次统计的任务数:
$$
L_{\text{new}}
\alpha L_{\text{old}}
+
(1-\alpha)N,
\qquad
\alpha=e^{-\Delta t/\tau}
$$
看 load 时,要结合逻辑 CPU 数量、CPU 实际使用情况,以及负载的变化趋势。
可以把 8 个逻辑 CPU 理解为 8 个工作位。假设任务主要做计算,每个任务都能用上这些 CPU:
| load 持续处于 | 可以怎样理解 |
|---|---|
| 约 4 | 平均约有 4 个任务在运行或等待 CPU,整体还有余量 |
| 约 8 | 8 个工作位基本都忙起来了 |
| 明显超过 8,例如 12 | 工作位不够用,部分任务需要排队 |
这个对照适用于任务主要做计算的情况。Linux 的 load 还计入部分不可中断等待的任务,例如等待磁盘或 NFS 响应的 D 状态任务。这些任务可能暂时不消耗 CPU,却仍会抬高 load。
2. Tasks:区分正在运行、等待和已经退出
1 | Tasks: 127 total, 2 running, 125 sleeping, 0 stopped, 0 zombie |
这行汇总了任务数量及状态:当前共 127 个任务,其中 2 个正在运行或等待 CPU,125 个处于睡眠状态,没有暂停的任务,也没有僵尸任务。
默认按进程统计;按大写 H 开启线程视图后,会改为按线程统计。

running 包含正在使用 CPU 和等待 CPU 的任务,sleeping 通常表示正在等待事件,并不代表异常。
3. %Cpu(s):CPU 时间花在哪里了
top 上半区的 %Cpu(s) 显示采样间隔内 CPU 时间的分配情况。默认汇总显示时,各项反映所有逻辑 CPU 的整体情况,相加约为 100%。
| 字段 | 含义 |
|---|---|
| us | 执行用户态代码的时间,不含单独计入 ni 的部分 |
| sy | 执行内核态代码的时间,如处理系统调用 |
| ni | 执行调整过 nice 值的任务的用户态代码时间 |
| id | 空闲时间,不含单独计入 wa 的部分 |
| wa | CPU 空闲期间,被内核计为等待 I/O 的时间 |
| hi | 处理硬中断的时间 |
| si | 处理软中断的时间 |
| st | 虚拟 CPU 本可运行,却未获得宿主机 CPU 调度的时间 |
这里的百分比以所有逻辑 CPU 的总时间为基准,与下半区进程 %CPU 的默认口径不同,后文会结合例子说明。
us 高:时间主要花在应用计算上
us 高,说明 CPU 较多时间用于执行应用程序的用户态代码。视频编码、数据压缩、加密和复杂运算,都可能推高 us。忙循环也会出现同样的现象,排查时要结合程序正在处理的业务,确认这些计算是否符合预期。
排查时,先按大写 P,按 CPU 使用率排序,找到消耗较高的进程并记下 PID;再运行 top -H -p PID,查看该进程内各线程的 CPU 使用情况。
sy 高:需要追查内核开销的来源
sy 高,说明 CPU 较多时间用于执行内核代码。这些开销可能由应用触发,例如频繁发起系统调用、大量创建和销毁进程,或进行较多内存管理操作。
假设一个程序每次只写几个字节,却每秒调用很多次写入接口,CPU 就可能花大量时间处理这些调用。看到 sy 高,可以从相关进程和近期业务变化入手,追查哪些操作增加了内核开销。
si 高:关注软中断,尤其是网络收发
si 高,说明 CPU 较多时间用于处理软中断。网络收发是常见原因,但软中断也承担其他工作。
例如,服务器收到大量小网络包,即使带宽没有跑满,也可能因为每秒处理的包数太多而消耗大量 CPU 时间。此时要结合网卡流量、每秒包数、丢包情况和各 CPU 的使用率判断。如果软中断集中在少数 CPU 上,还应检查网卡队列和中断分配情况。
整体使用率不高,应用为什么还会卡?
整体平均值可能掩盖单线程瓶颈。例如,一台机器有 8 个逻辑 CPU,应用的关键线程持续用满一个,其余基本空闲。不同位置看到的数据可能如下:
| 观察位置 | 可能看到的情况 |
|---|---|
| 上半区 CPU 汇总行 | 整体忙碌约 12.5%,id 约 87.5% |
| 下半区进程列表 | 该进程 %CPU 接近 100% |
按 1 展开后 |
某个逻辑 CPU 接近用满,其余较空闲 |
机器整体还有余量,但关键线程已经用满一个逻辑 CPU 的处理能力。此时需要检查这部分计算能否并行处理、是否存在忙循环,以及是否受到 CPU 绑定等限制。
按数字 1 可以展开每个逻辑 CPU 的使用情况,再次按 1 恢复汇总。连续观察几次,能帮助判断负载是否集中在少数 CPU 上。线程可能在不同 CPU 之间迁移,因此繁忙的 CPU 不一定始终是同一个。
如果各个 CPU 都较空闲,就应继续检查锁竞争、I/O 和下游服务响应等等待因素。
4. 内存与交换:先看 avail Mem
判断系统还有多少内存可用,优先看 **avail Mem**。它估算了应用还能使用的物理内存,包含一部分可以回收的缓存。虽然它常显示在 Swap 行末尾,但表示的仍是物理内存。
free 只表示完全空闲的内存。Linux 会利用空闲内存缓存文件,提高读写效率,这部分会体现在 buff/cache 中。因此,**free 很小,不一定缺内存**,还要看 avail Mem 剩余多少、是否持续下降。
Swap 是用来存放换出内存数据的磁盘空间。Swap 行的 used 大于零,可能只是之前换出的数据仍留在那里,不能据此判断当前内存紧张。
四、下半区:读懂进程占用了多少资源
进程区的列可以自定义。下面是常见布局中的十二列,实际显示以当前配置为准。
| 列 | 含义 | 阅读时的要点 |
|---|---|---|
| PID | 进程号 | 线程视图下对应线程标识 |
| USER | 有效用户名 | 用于识别所属用户 |
| PR | 调度优先级 | 普通任务常见为 20 + NI,实时任务可能显示 rt |
| NI | nice 值 | 普通调度任务通常为 -20~19,数值越小,调度权重越高 |
| VIRT | 虚拟地址空间大小 | 包含映射和保留的地址空间 |
| RES | 驻留物理内存 | 包含共享页 |
| SHR | RES 中可共享的部分 | 不等于当前实际被其他进程共同使用的全部页 |
| S | 任务状态 | 如 R、S、D、Z |
| %CPU | 采样间隔内的 CPU 使用率 | 需确认 Irix / Solaris 模式 |
| %MEM | RES 占总物理内存的比例 | 与 RES 一样包含共享页 |
| TIME+ | 累计 CPU 时间 | 多线程的执行时间可以累加 |
| COMMAND | 命令名或命令行 | 按 c 切换 |
1. S:任务处于什么状态
top 上半区的 Tasks 是数量汇总;下半区的 S 列是每个任务的状态。

列名 S 是 State(状态)的意思,列里的字母才是具体状态。
| 字母 | 英文 | 含义 |
|---|---|---|
| R | Running | 正在运行,或等待 CPU |
| S | Sleeping | 可中断睡眠,通常在等待请求、事件等 |
| D | Uninterruptible sleep | 不可中断睡眠,常见于等待 I/O |
| I | Idle | 空闲的内核线程 |
| T | Stopped | 被暂停 |
| t | Tracing stop | 因调试跟踪而停止 |
| Z | Zombie | 已退出,但父进程尚未回收退出信息 |
D 状态为什么可能杀不掉?
例如,一个任务读取 NFS 文件时,服务端迟迟不响应,任务陷入不可中断等待。此时发送 kill -9,终止信号可能已经挂起,但任务还不能立即完成退出,需要等待相关内核等待结束。
所以,“发出了强制终止信号”不等于“任务立刻消失”。反复发送信号通常无法解决底层等待,应继续查磁盘、网络或 NFS 服务端。
Z 状态为什么也杀不掉?
因为它已经退出了,没有仍在运行的程序可杀。系统只是保留它的 PID、退出状态等少量记录,等父进程来领取。
例如,父进程启动一个子进程处理文件,子进程处理完退出了,父进程却没有回收退出信息,就会留下 Z 状态。这时需要检查的是父进程的回收逻辑;如果僵尸持续增加,才考虑在评估业务影响后重启父服务,并修复程序。
2. %CPU:这个进程用了多少 CPU 时间?
下半区的 %CPU 表示进程在采样间隔内使用 CPU 的情况。默认常见设置下,计算方式如下:
$$
\frac{\text{采样期间进程消耗的CPU时间}}
{\text{采样期间实际经过的时间}}
\times100%
$$
假设 top 每秒刷新一次:一个线程在这一秒内,累计使用 CPU 计算了半秒,其余时间在等待,那么进程的 %CPU 就约为 50%;如果持续计算了一整秒,就约为 100%。
这里的 CPU 时间,是任务实际占用 CPU 执行的时间。等待 CPU 调度、等待磁盘或者等待请求的时间,都不算它正在使用 CPU。
为什么一个进程可以超过 100%?
因为一个进程可以包含多个线程,而多个线程可以同时在不同逻辑 CPU 上执行。进程的 CPU 时间,是这些线程消耗的 CPU 时间之和。
例如,实际经过了 1 秒,两个线程分别在两个逻辑 CPU 上各计算了 1 秒,这个进程就累计消耗了 2 秒 CPU 时间:
$$
\frac{2,\text{秒}}{1,\text{秒}}\times100%=200%
$$
| 1 秒内的执行情况 | 累计 CPU 时间 | 进程 %CPU |
|---|---|---|
| 一个线程计算了半秒 | 0.5 秒 | 约 50% |
| 一个线程持续计算一秒 | 1 秒 | 约 100% |
| 两个线程分别持续计算一秒 | 2 秒 | 约 200% |
因此,进程显示 100%,表示消耗了相当于一个逻辑 CPU 的全部时间,不代表整台机器的 CPU 都用满了。
为什么上半区和下半区的百分比看起来不同?
上半区 %Cpu(s) 汇总显示时,以所有逻辑 CPU 的总时间为基准;下半区默认以一个逻辑 CPU 的时间为基准。
例如,一台有 8 个逻辑 CPU 的机器,某个进程用满其中两个,其余 CPU 都空闲。下半区该进程的 %CPU 约为 **200%**,上半区整体忙碌比例则约为 **25%**,因为它使用了整机八分之二的 CPU 时间。
上面这种“一个逻辑 CPU 对应 100%”的显示方式,叫作 Irix 模式,通常是默认模式。
按大写 I 切换到 Solaris 模式后,下半区进程的 %CPU 会再除以逻辑 CPU 总数。在上述 8 个逻辑 CPU 的例子中,200% 就变成了 25%。
这只改变显示比例,不会改变进程实际获得的 CPU 时间。日常阅读时,先理解默认口径;比较不同截图或采集结果时,再确认是否切换过模式。
3. 看进程占用多少内存,先看 RES
想知道一个进程当前占用了多少物理内存,可以先看 RES。例如,RES 显示为 500m,表示它当前驻留在物理内存中的数据和代码等约为 500 MiB。排查内存增长时,可以重点观察这一列。
旁边的 VIRT 可能大得多,但不代表实际占用了这么多物理内存。程序可以先预留较大的虚拟地址空间,等真正使用时再分配物理内存。因此,看到 VIRT 有几十 GB,不必立即判断为内存异常。
需要精细统计多个进程的内存时,可以进一步查看 PSS。多个进程可能共用一份物理内存,各自的 RES 都会把它算进去,直接相加会重复计算。
PSS 会把共享部分分摊:例如,两个进程共用 100 MiB,每个分摊 50 MiB。
排查内存增长时,可以连续观察进程的 RES,并对照系统可用内存的变化。
VIRT 看进程使用和预留了多少虚拟地址空间,RES 看其中有多少当前驻留在物理内存中,PSS 则在驻留内存的基础上,将共享部分按比例分摊、独占部分全部计入。

4. TIME+:累计执行了多久
TIME+ 表示进程累计使用的 CPU 时间,等待时不计入。
例如,一个服务虽然已经启动三个月,但平时大多在等待请求,TIME+ 可能只有几分钟。
多线程进程会累加各线程的 CPU 时间。如果四个线程分别在四个逻辑 CPU 上持续计算十分钟,TIME+ 就可以接近四十分钟。
日常排障时,%CPU 看最近这段时间忙不忙,TIME+ 看累计用了多少 CPU 时间。不要仅凭 TIME+ 很大就判断进程异常,也可能只是它运行得久。
五、容器里的 top:看到的资源,不一定都能用
在容器里运行 top,上半区仍可能显示宿主机的 CPU 和内存统计,下半区却只列出容器内的进程。
这是因为,容器通常通过 PID namespace(进程命名空间)隔离进程视图,所以进程列表只显示容器内可见的进程;CPU 和内存用量则由 cgroup 管理和限制。这两套机制各自发挥作用,资源限制生效,并不意味着 top 读取的系统统计也会自动变成容器的额度。
top 读取的 /proc/stat、/proc/meminfo 等文件,仍可能提供宿主机层面的统计。有些环境会通过 LXCFS 等机制,让这些文件反映容器的资源情况,因此不同环境中的显示可能不同。
例如,宿主机有 64 个逻辑 CPU、256 GB 内存,但容器的限额只有相当于 2 个 CPU 的计算资源和 4 GB 内存。即使 top 显示宿主机还有大量空闲,容器仍可能因 CPU 配额用尽而被限流,或因达到内存上限且无法回收足够内存而触发 OOM,导致进程被终止。
排查 Docker 容器时,可以先在宿主机运行 docker stats 容器名,查看容器的 CPU 使用率及内存用量,并与配置的资源上限对照。例如,CPU 限额为 2 个时,使用率持续接近 200%,就需要进一步检查是否发生了 CPU 限流。
六、生产环境常用快捷键
下表是生产环境常用快捷键
| 目的 | 按键或命令 | 作用 |
|---|---|---|
| 找当前 CPU 使用量较大的进程 | P | 按 %CPU 排序 |
| 检查是否有单个 CPU 忙满 | 1 | 展开各逻辑 CPU |
| 查看进程内线程 | top -H -p PID |
显示目标进程的操作系统线程 |
| 找内存占用较大的进程 | M | 按 %MEM 排序 |
| 调整内存单位 | E / e | 分别调整汇总区和进程区单位 |
| 增删字段 | f | 打开字段管理 |
| 区分同名进程 | c | 切换命令名和命令行 |
| 查看父子关系 | V | 切换树形视图 |
| 过滤用户 | u | 按有效用户筛选 |
| 查找进程列表中的文本 | L | 搜索字符串 |
| 突出当前排序字段 | x | 高亮排序列 |
| 按累计 CPU 时间排序 | T | 按 TIME+ 排序 |
| 保存当前布局 | W | 写入配置,供下次使用 |
常用快捷键不必全部记住,运行 top 时按小写 h 或 ?,即可查看交互操作帮助;
需要了解完整的参数、字段和快捷键说明,可以在终端执行 man top。注意按键区分大小写。

2