ARTICLE DETAIL

资讯详情

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

Android性能排查利器:adb shell top命令从入门到实战

Android性能排查利器:adb shell top命令从入门到实战 在Android开发和测试的工作里adb shell top是我几乎每天都会敲的命令。线上反馈App启动慢、界面滑动掉帧测试说手机发烫、耗电异常我第一反应不是去猜而是连上设备敲一条 top先看看当前系统里到底谁在吃CPU、谁在霸占内存。它虽然不像perfetto那样能画出精细时间线也不像simpleperf那样能采到函数级热点但它是排查性能问题的第一板斧足够轻、足够快、几乎任何Android设备都能用。这篇文章把adb shell top从参数到输出、从原理到排障完整梳理一遍。适合刚接触adb调试的初级开发也适合已经在用top但没有系统研究过输出字段的进阶用户。内容全部来自实际调试经验每个命令都经过真机验证可以直接抄作业。1. top命令到底能干什么1.1 一个命令解决三类问题top在Android系统中的地位相当于Windows上的任务管理器而且是带实时刷新、可排序、可指定进程的加强版。日常使用中我主要拿它解决三类问题。第一类是CPU占用问题。App无端发热、耗电异常、后台疯狂跑任务这些现象的根源往往是某个进程的CPU占用率异常。执行adb shell top后按CPU排序谁的占用率高一目了然。第二类是内存占用问题。虽然top对内存的统计不如dumpsys meminfo那么精确但通过观察VIRT、RES这些字段的数值变化能快速判断一个进程是否存在内存异常增长的趋势。第三类是线程级定位问题。top支持按线程模式显示可以清楚看到某个进程内部的哪个线程在消耗CPU这是排查卡顿和死循环的利器。值得一提的还有一点top本身是只读命令它只是读取系统状态并展示不会修改任何系统配置。所以在线上设备、用户反馈机上跑top是安全的不会对设备状态造成影响。1.2 和Linux桌面版top有什么差异Android底层的top命令来自toybox这是一个轻量级的命令集合很多熟悉Linux桌面环境的同学第一次在Android上跑top都会觉得既熟悉又陌生。功能逻辑是类似的但字段细节有不少差异。Linux桌面版的top顶部会显示系统负载平均值load average、任务汇总、CPU状态占比us/sy/id/wa等和内存使用汇总。Android的top则更侧重进程列表顶部的系统汇总信息相对简单有些版本甚至不显示CPU核心数和负载。另外Android的进程所属用户是按应用沙箱机制划分的top输出里会出现u0_aXX这样的用户名这在Linux桌面上基本见不到。同一个包名在不同设备上的uid可能不同这是Android多用户机制决定的。参数方面Android top支持的参数比Linux版精简交互式快捷键也少很多。不过日常用的-d、-n、-m、-s这些核心参数都是可用的。千万别把CentOS上那套top的交互快捷键习惯直接搬过来有些在Android上按下没反应有些键干脆就是退出。1.3 哪些场景应该换更专业的工具top好用但不是万能的。它适合用来快速定位问题的大方向一旦锁定目标后续深挖往往需要换工具。举几个典型场景如果想看App启动过程中每个阶段的CPU/内存精确变化曲线用top手动记录太粗糙应该上perfetto或systrace做时间线分析。如果想分析到底是哪个函数占用CPU过高top只能看到线程维度得用simpleperf record抓取调用栈。如果想精确定位内存泄漏的引用链top看RES只能是发现苗头真正定位要借助dumpsys meminfo和hprof堆转储。我的经验是top负责“把嫌疑人从人群中揪出来”具体定罪量刑再换更精准的手段。这个思路能帮你少走很多弯路不会一上来就被海量数据淹没。2. 输出字段逐个拆解2.1 先看懂两代top的输出格式Android系统在不同版本上的top输出格式差异很大这点非常坑。Android 8.0之前用的是toolbox版本的top输出是下面这种风格User 0%, System 15%, IOW 0%, IRQ 0% User 30 Nice 0 Sys 230 Idle 401 IOW 0 IRQ 0 SIRQ 0 661 PID PR CPU% S #THR RSS PCY UID Name 234 0 8% S 12 23456 fg u0_a89 com.example.app它的进程字段是PID、PR、CPU%、S、#THR、RSS、PCY、UID、Name一眼能看出来线程数、即时CPU占用和进程优先级。Android 8.0之后系统切换到toybox版本的top输出变成了下面这种更为大家熟知的风格Tasks: 421 total, 1 running, 260 sleeping, 0 stopped, 0 zombie Mem: 5772416K used, 3629932K free, 48516K shmem, 16004K sreclaim, 5180K sunreclaim, 394464K kernel, 1726120K page_tables PID USER PR NI VIRT RES SHR S[%CPU] %MEM TIME ARGS 1234 u0_a89 10 -2 4.2G 132M 91M S 12.5 3.2 0:15.30 com.android.systemui这个格式和Linux的top更接近。系统会先输出任务汇总和内存汇总然后是进程明细。判断设备用的哪个版本很简单看S状态后面跟的是#THR还是VIRT后者一定是新版toybox。2.2 关键字段的精读与判断标准先讲新版toybox的字段。PID不用多说进程号。USER是进程所属用户Android应用大多显示为u0_aXX形式这个XX加10000就是应用UID比如u0_a89对应UID 10089用adb shell id可以对照验证。PR和NI是优先级相关的两个字段。PR是进程优先级数值越小优先级越高NI是nice值范围一般是-20到19普通App进程通常为0或负数。系统关键进程如surfaceflinger往往有更高的调度优先级这在排查音画不同步问题时很有参考意义。VIRT是虚拟内存大小包含进程可能访问的所有地址空间这个数值往往很大但它不代表实际物理内存占用。真正需要关注的是RES也就是常驻内存大小表示进程当前映射到物理内存的部分。SHR是共享内存映射的共享库会算在里面。很多时候看到VIRT几个G不用慌RES才是衡量内存压力的关键。S是进程状态R是正在运行S是可中断睡眠D是不可中断睡眠常见于IO等待Z是僵尸进程。一台设备如果长期存在僵尸进程说明父进程没有正确回收子进程需要警惕。%CPU是最核心的字段之一它表示采样周期内进程消耗的CPU时间占比。这里有个新手容易懵的地方多核设备上百分比最大可以超过100%四核设备理论上单进程最高能到400%。后面我会专门讲多核场景怎么判断。%MEM是RES占系统总内存的比例TIME是进程累计消耗的CPU时间格式是分钟:秒.百分秒这是判断进程是否长期霸占CPU的好指标。ARGS是进程的命令行参数一般会显示包名或进程名。旧版toolbox的字段逻辑类似但差异点要特别留意。CPU%在旧版里默认是即时值跳动幅度很大。RSS以KB为单位PCY显示前台fg还是后台bg这可以快速判断一个进程是否因为切换到后台还在疯狂干活。UID直接显示数字省去了从u0_aXX换算的步骤。2.3 怎么从输出中读出“异常信号”光看懂字段不够得知道什么样的数值属于危险信号。根据我这几年的排查经验有几种典型模式非常值得警惕。单个进程CPU持续超过200%这个基本可以断定有问题。正常App即便是播放视频、处理图片CPU峰值也多是瞬时冲到100%左右长时间保持在200%以上意味着大概率存在死循环或者多线程任务异常叠加。S状态为D的进程数量过多说明系统IO出现阻塞这时候往往伴随整机卡顿问题在存储或者文件系统层面而不在单个App。僵尸进程持续累积如果top输出里Z状态进程越来越多说明某个父进程不断创建子进程又不好好回收这在部分使用多进程架构的App里出现过。RES内存的观察有一个技巧连续刷新多看几轮。如果某个进程的RES只增不减每次刷新都高一点点那基本是内存泄漏的苗头。TIME的增速同样关键如果%CPU看着不高但TIME涨得飞快说明进程在采样周期内有过瞬时高频占用这种“脉冲式”CPU消耗往往和定时任务、数据上报、图片加载这类操作有关。3. 高频参数与组合玩法3.1 必须掌握的五个核心参数top的参数看着多日常真正高频用到的也就五六个。我整理了一个速查表。参数含义示例-d刷新间隔单位秒top -d 2-n刷新次数配合-b使用top -n 1-s按指定字段排序常用cpu、memtop -s cpu-m最多显示行数top -m 10-p指定进程PIDtop -p 1234-H按线程模式显示top -H -p 1234-b批处理模式不进入交互界面top -b -n 1-d后面的数值可以是小数比如-d 0.5表示半秒刷新一次。但我实际建议日常保持在1秒以上刷新太频繁不仅输出刷得人眼花top本身也会消耗CPU干扰判断结果。-n几乎总是和-b搭配使用因为只有在批处理模式下top才会输出指定次数后自动退出否则会一直停留在交互界面。-s的排序字段在不同版本上支持的情况不太一样最通用的是cpu和memtime在部分版本也能用。-p有一个坑要重点提醒toybox版本的top对-p参数的空格处理不够统一有的设备上top -p 1234好使有的必须写成top -p1234。如果遇到bad parameter之类的报错先试试不加空格的写法。-H用于线程模式它会列出指定进程下每个线程的CPU占用这是定位“进程内某个线程异常”的关键参数。3.2 三个最实用的命令组合单看参数没感觉直接上我平时最常用的三组命令。第一组一次性当前快照。这个几乎每天都会用看看当前系统里CPU占用最高的进程有哪些adb shell top -b -n 1 -m 20 -s cpu解释一下每个参数的作用-b让top以批处理方式运行输出一次后退出-n 1指定只刷新一轮-m 20限制只显示前20行避免刷屏-s cpu按CPU占用率从高到低排序。这条命令输出稳定、快速、可重复适合在自动化脚本里调用。第二组持续观察某个目标进程。先拿到目标App的PID再单独盯着它看adb shell pidof com.example.app adb shell top -d 1 -p 1234pidof拿到的数字填进-p参数top就会只显示这个进程的信息。不过要注意这种方式是交互式运行会一直刷新直到按q退出。在脚本里建议加上-b -n限定轮次。第三组线程级定位。比如系统界面卡顿怀疑systemui内部有线程异常但是只看进程维度看不出问题adb shell top -H -p 1234 -b -n 1输出里每一行是一个线程ARGS列会尽量显示线程名。这个命令是排查卡顿问题的核心武器配合第4章的实战案例看会更明白。3.3 进阶多轮采样落盘做对比top是动态刷新的单次快照只能代表当前瞬间很多性能问题具有“脉冲”特征时好时坏。遇到这种情况我建议做多轮采样并保存到文件。adb shell top -b -d 3 -n 20 -s cpu top_$(date %H%M%S).log这条命令每3秒采样一次共20轮持续1分钟把结果存到文件里。完成后先grep -A 20 PID看每轮的头几行再对比不同时间点的CPU占用变化。如果某个进程的CPU占用从低到高爬升配合操作时间点就能判断哪个操作触发了问题。如果嫌手动分析麻烦可以用主机上的shell脚本做个简单的批量采集。比如循环启动App的某个页面每次截取目标进程的那一行for i in 1 2 3 4 5; do adb shell top -b -n 3 -d 1 -s cpu | grep com.example cpu_sample.log sleep 2 done这个脚本在主机上执行每次循环里top运行3轮grep筛出目标行追加到日志。跑完之后打开cpu_sample.log目标进程的CPU波动曲线基本就出来了。4. 性能问题排查实战4.1 从CPU占用率追踪到具体线程我之前处理过一个真实案例App在打开某个详情页时明显卡顿但并不是每次都发生而且稍等几秒又恢复了。这种偶现卡顿用perfetto去录很容易错过时机反而是top的实时性更合适。复现问题时我先在设备上跑adb shell top -b -d 1 -n 30 -m 30 -s cpu用最长30次的轮询记录整个过程。卡顿发生的瞬间日志里显示App进程CPU占用从正常的几个点突然跳到150%左右。拿到这个证据后再用adb shell top -H -p PID -b -n 1看线程列表发现一个名为ImageDecoder的线程CPU占用接近100%。这时候基本可以断定是图片解码压力过大导致。再去查详情页的图片配置发现一张原图分辨率接近8000像素解码时内存和CPU都扛不住。后来在加载前加了采样压缩问题直接消失。整个过程top就完成了“锁定进程”和“定位线程”两步关键判断。这里有个经验分享用top定位线程时多取几次采样结果对比。线程名可能因为被截断而不完整比如ImageDecoder在某些ROM上显示为ImageDecod这时候结合代码命名习惯去猜或者用下面的命令确认adb shell cat /proc/PID/task/TID/comm线程号TID从top的PID列读取这条命令会返回该线程在内核里的真实名称一般不会截断。4.2 内存异常从top线索到meminfo定罪top不能直接证明内存泄漏但它能快速揪出内存异常增长的进程。排查内存问题的思路和CPU稍有不同不能只看某一时刻的RES值要看趋势。我用top排查内存问题时一般会连续采样十几次每次间隔1秒。如果RES数值只涨不跌比如每轮都涨几MB那基本可以确认存在泄漏嫌疑。但top看到的是趋势不能直接拿来当证据因为RES里面包含共享库的内存映射单个App的RES升高可能是共享库整体加载导致的不代表应用自己的私有内存增长。确认阶段要用上dumpsys meminfoadb shell dumpsys meminfo com.example.app输出里重点看PSS Total、Java Heap、Native Heap这几项。Java Heap持续增长说明Java层有对象没有被回收Native Heap异常则要排查JNI相关代码。如果PSS Total和RES的变化趋势一致而Heap本身没问题那可能是Bitmap、线程栈这类native分配在增长。之前有个图片列表页内存飙升的casetop发现RES在3秒内从200M涨到400M随后dumpsys meminfo显示Graphics这部分暴涨。定位到是列表页RecyclerView对图片缓存处理不当每帧滑动都会触发新的Bitmap分配且没有复用这个问题靠top发现苗头靠meminfo确认区域最后再针对性优化。4.3 多核CPU百分比到底怎么解读很多使用top的同学都困惑过为什么一个进程的CPU%加起来能超过100%是不是命令算错了其实没有Android设备基本都是多核CPUtop显示的%CPU是进程消耗的所有核心CPU时间片总和的占比。四核设备上一个进程同时占满四个核心显示的就是400%。那多少算异常我的判断标准是分场景的。稳定运行的普通App空闲时CPU基本在0-5%滑动列表、加载网络内容时可能冲到30%-50%播放解码视频时50%-100%是常态如果持续超过150%或者出现明显的锯齿状跳变就要重点怀疑有死循环或异常任务调度。还要注意大小核架构的影响。现在的手机普遍是big.LITTLE架构有高性能大核和低功耗小核。一个进程即使CPU%相同跑在大核和小核上的实际功耗和热度差异很大。top只能看到百分比看不到跑在哪个核心上。如果发现CPU%不高但发热严重需要进一步查看核心频率adb shell cat /sys/devices/system/cpu/possible adb shell cat /sys/devices/system/cpu/cpu4/cpufreq/scaling_cur_freq第一条命令查看CPU编号范围第二条查看某个核心的当前频率。频率长时间停留在最高档即使占用率看起来不高也会带来明显的发热。4.4 结合logcat抓取时间线证据top能告诉我们“谁出问题了”但往往还要回答“为什么出问题”这时候要配合logcat。我的标准操作流程是先起一路top记录CPU变化再起一路logcat记录系统日志同时操作复现问题最后把两份日志按时间戳对齐。adb shell top -b -d 1 -n 120 -s cpu top.log adb logcat -v threadtime logcat.log -v threadtime让logcat输出带线程ID和时间戳这是对齐分析的关键。跑完后从top.log里找到CPU飙升的时间点再到logcat.log里看同一时间点发生了什么比如GC频繁、Binder调用超时、ANR弹窗这些问题往往会在logcat里留下痕迹。需要提醒的是top.log和logcat.log各自的时间戳都是设备本地时间只要是在同一台设备上采集的直接按时间对应就行。如果是长时间采集注意两个进程同时运行时对设备性能的影响尤其是在低端机上top本身和logcat都会吃一点资源但通常不会影响问题定位的准确性。5. 常见问题与排查技巧实录5.1 连接设备时最常见的两个坑top命令本身很简单真正卡住大家的反而是前面“adb shell”这一步。常见问题里第一个就是adb devices显示unauthorized。这个是因为设备上的USB调试授权弹窗没有确认或者之前点了拒绝。解决办法是重新拔插USB线然后在设备的USB调试设置里撤销授权、重新弹窗授权命令行里执行adb kill-server adb start-server再重新看。第二个坑是模拟器连接。不少同学用夜神、MuMu这类模拟器跑Android但adb devices里看不到设备。这是因为模拟器自带了自己的adb连接端口夜神模拟器常见端口是62001MuMu是7555需要手动连接adb connect 127.0.0.1:62001连接成功后adb shell top就可以正常使用了。模拟器和真机的top输出格式基本一致但CPU占用率会受模拟层影响比如x86模拟器跑ARM应用时翻译层会额外消耗CPU看到奇怪的高占用先别慌。5.2 top参数不兼容怎么办老设备和老ROM上top的参数支持情况参差不齐。我遇到过几种典型情况。一种是-H参数不存在这在Android 8之前的设备上很常见。老版本toolbox的top用的是-t来显示线程信息写法是adb shell top -t -p PID。如果-H报错立刻换-t试试。另一类是-p参数的写法差异前面提过有的版本只接受-p1234而不是-p 1234。还有的ROM对top做了定制输出字段顺序和标准版完全不一样最稳妥的办法是不排序直接先跑两秒看输出再决定后面怎么处理。如果top命令直接报not found说明这个ROM精简过度把toybox的top组件砍掉了。这种情况可以用另外两个命令组合替代adb shell ps -o PID,%CPU,%MEM,NAME -A adb shell cat /proc/statps -o能列出进程级CPU和内存信息cat /proc/stat能看到整机CPU时间汇总。虽然不如top直观但关键信息都有。再彻底一点的办法是安装busybox它自带top组件但对于一般的调试场景我个人不太建议为了一条命令往设备里多塞一个工具包。5.3 信息显示不完整和权限问题top不是万能的很多信息在权限受限的情况下拿不到。尤其是Android 10之后普通shell用户对系统进程的可见性比之前严格了有时候top里看某些系统进程只能看到PID和基本状态看不到完整的ARGS参数线程模式下甚至不显示线程名。这时候先判断设备是不是userdebug或者eng版本。如果是执行adb root重启adb为root权限再看top通常就完整了。如果设备是普通的用户版本又没有root权限那top能看到的就有限但应用层进程的信息基本还是够用的。想看的进程如果显示被截断可以用ps -A | grep来弥补ps命令对参数的展示通常比top更完整。还有一个SELinux相关的坑。部分设备上即使adb root了由于SELinux enforcing模式的限制top依然看不到部分内核线程的详细信息。在测试机上可以临时执行setenforce 0关掉SELinux再看生产环境千万不要这么做纯属为了调试方便开的临时口子用完记得恢复。5.4 用top时我自己踩过的几个坑最后说几个容易忽略的细节。第一个是刷新间隔别太短。我最早调试时为了看到实时变化用了-d 0.1结果top本身CPU占用到了接近10%直接干扰了排查结果。后来习惯至少1秒间隔如果需要更细的采样宁可拉长采样时间也不缩短间隔。第二个是%CPU的跳动容易误判。top显示的CPU占用是采样周期内的平均值不是瞬时值。一次采样周期内进程前0.5秒满载、后0.5秒空闲显示出来就是50%左右。所以看到某个进程CPU“很高”先多采样几轮确认是持续高还是瞬时尖峰再动手排查避免被假信号带偏。第三个是对“Mem:”那行别按Linux桌面的思维去理解。Android的内存管理用了大量文件页缓存和zram压缩整机内存的“used”数值偏高不代表可用内存不足。真正的系统内存压力要看可用内存和低内存杀手lmkd的日志top进程列表里的RES才更贴近实际业务的占用情况。结尾我个人在实际操作中的体会是top命令看起来简单但能不能用好决定了性能排查的效率。它不像perfetto那样能输出一张漂亮的火焰图也不像simpleperf那样能定位到函数级热点但在所有Android设备上第一反应永远是先来一条top把嫌疑进程锁住。这里再分享一个实用的小技巧平时可以在脚本里封装一条tm命令内容就是adb shell top -b -n 1 -m 20 -s cpu遇到性能问题先跑一下几秒钟就能拿到第一手证据。后面再换更深入的工具思路就清晰多了。
返回列表