手机用着用着突然卡一下,打开应用白屏半天,切后台再回来就重新加载,玩个游戏还动不动杀后台——这些问题十有八九都出在内存上。想定位到底是谁在偷内存,光靠手机自带的“存储空间”页面根本看不明白,这时候就该轮到 ADB 出场了。通过 ADB 连接 Android 设备以后,你可以像坐在电脑前操作服务器一样,直接查看系统里每一块内存的分配情况、每个进程的实时占用,甚至把采样数据拉成一条曲线,清清楚楚看出内存在哪一秒被吃光。这篇文章我会从 ADB 环境搭建开始,把实时监控内存的完整方法、常用命令、自动化脚本、常见坑全部讲透,适合 Android 开发者、测试工程师、搞机爱好者和所有被“内存焦虑”折磨过的普通用户参考。
1. 准备工作:先把 ADB 环境搭好,设备连上
1.1 为什么要用命令行监控内存,而不是装个 App
很多人上来就问:既然要在手机上监控内存,直接装个“内存清理大师”不就行了?这里要说明白一个关键区别:普通 App 跑在 Android 的应用沙箱里,它看到的系统信息是系统“允许”它看到的,很多真实数据拿不到,而且这类 App 自身也要占用内存,属于“带个护士去体检,护士也占了病床”。ADB 是 Android Debug Bridge 的缩写,它是 Google 官方提供的调试工具,通过 USB 或无线连接以后,你能拿到和系统工程师几乎一样的数据权限,比如 dumpsys meminfo 输出的进程详细内存清单、/proc/meminfo 里的内存总账、top 命令里的实时进程消耗。这些信息在普通 App 里是看不到的,或者说看不全。我自己的习惯是:凡是遇到性能问题、内存异常、卡顿怀疑,第一件事永远是插上数据线,先看数据,再猜原因。
1.2 安装 ADB:其实不需要装一整个 Android Studio
很多新手一搜“ADB 下载”,结果被引导去装几个 GB 的 Android Studio,其实没必要。你只需要 Google 官方的 Platform Tools 压缩包,里面就带了 adb、fastboot、sqlite3 等基础工具,加起来也就十几 MB。Windows 用户下载解压后,把解压目录配置到系统环境变量里,这样在任何终端窗口输入 adb 都能直接调用。macOS 用户如果有 Homebrew,一条 brew install android-platform-tools 就搞定;Linux 用户用 apt install android-tools-adb 或者对应发行版的包名就行。配置完环境变量以后,打开终端输入 adb version,能打印出版本号就说明装好了。
注意:别去乱七八糟的第三方站点下载 adb 驱动或工具包,很多捆绑了广告甚至更糟的东西。就用 Google 官方 Platform Tools,或者 Android Studio 自带的 platform-tools 目录,稳妥第一。
1.3 设备连接:开发者选项和 USB 调试是入场券
要在手机上开启 USB 调试,路径大同小异:设置 -> 关于手机 -> 连点“版本号”七次,系统提示“你已经处于开发者模式”,然后返回设置找“开发者选项”,打开“USB 调试”。这一步没难度,真正的坑在后头。我遇到过最典型的情况是:手机明明开了 USB 调试,插上电脑后 adb devices 依然一片空白。以我手头的 honor 90 为例,升级到 MagicOS 8.0、Android 14 以后,插上电脑只有充电图标,adb devices 怎么都刷不出来。后来排查下来是两个原因叠加:一是这台电脑从来没装过通用的 ADB 接口驱动,设备管理器里显示的是带感叹号的未知设备;二是 Windows 11 自带的 USB 驱动对 Android 的“复合设备”支持不友好。解决办法是到设备管理器手动指定驱动目录为 Platform Tools 所在的 usb_driver 文件夹,装好“Android Composite ADB Interface”以后重新插拔,adb devices 立马就出序列号了。另外要提醒一句:确认手机端的 USB 连接模式是“文件传输/MTP”,不是“仅充电”,有些机型在“仅充电”模式下不会开启 ADB 接口。
1.4 一些厂商特有的“鉴权”机制,得提前知道
除了常规的 RSA 指纹授权弹窗,现在不少厂商在 USB 调试入口上加了二次校验。我见过比较典型的是小天才手表这类产品,开启 ADB 以后电脑端会提示输入动态校验码,这个码是在厂商规定的校验网页上根据设备信息现场计算的,本质上是为了防止别人拿到设备后悄悄开启 ADB。遇到这种机制,直接去设备的说明书或系统提示里找校验入口即可,按提示操作就能连上。还有一部分电视、车机、盒子类设备,比如一些老款创维电视,开启 ADB 的方式并不是在“开发者选项”里,而是要在“设置 -> 本机信息”里连按确认键若干次,或者在工厂菜单里打开“ADB 开关”。这类设备的系统版本通常不标准,建议先搜索确认具体机型的开启路径,避免瞎折腾浪费时间。
2. 快速入手:三条命令看透手机内存现状
2.1 dumpsys meminfo:把内存账本翻出来
连接好设备以后,第一条必练的命令是 adb shell dumpsys meminfo。直接执行这条命令,你会看到整个系统的内存概况,包括总内存、可用内存,以及按进程排列的详细内存占用清单。输出内容很长,重点是每个进程那几列:
- PSS(Proportional Set Size):进程按比例分摊共享内存后的占用,比如一块 30 MB 的共享库被 3 个进程使用,每个进程只算 10 MB 进来。这个值最接近“真实占用”,也是系统判定进程是否该杀的重要指标。
- RSS(Resident Set Size):进程实际驻留在物理内存中的总大小,会包含共享库的完整大小,所以几个进程的 RSS 加起来可能远超真实使用。
- USS(Unique Set Size):进程独占的物理内存,不包含任何共享部分,表示“这个进程消失后能释放多少内存”。
一般看内存压力,优先看 PSS;想看某个 App 到底多能吃内存,USS 更诚实的。手机上跑微信、淘宝这类大应用时,你会发现它们的 PSS 动辄五六百 MB,USS 可能只有两三百 MB,剩下都是和系统共享的 WebView、编解码库、渲染库占用,这属于正常现象,不一定是“内存泄漏”。
2.2 只看某个应用该怎么办
如果你想单独盯一款 App 的内存,命令要加上包名,比如 adb shell dumpsys meminfo com.tencent.mm,输出里面会把当前包的所有进程、各个内存分区列出来。我平时排查应用卡顿,会重点看这两行:
- Java Heap:Java 层堆内存,由运行时负责分配和回收,数值异常上涨说明可能存在对象泄漏。
- Native Heap:Native 层堆内存,由 C/C++ 代码负责,图片解码、音视频处理等都会在这里占地方。
如果反复操作某个页面后 Java Heap 只涨不降,基本就可以怀疑有内存泄漏了。这里补充一个细节:加包名以后 dumpsys 出来的 Total 会比系统全家桶输出里的对应行略小,因为它只统计了该包名下能归类的部分,子进程得用 -p 参数或者直接看系统全局清单。
2.3 proc/meminfo:系统内存的“总账本”
除了看进程,全局内存状态还得看 /proc/meminfo。用 adb shell cat /proc/meminfo 查看时,第一行 MemTotal 是物理内存总量,往下是 MemFree、MemAvailable、Buffers、Cached、SwapTotal 这些字段。这里我不建议死盯 MemFree,因为 Android 系统会有意识地用空闲内存做文件缓存来加速读写,MemFree 常年很低非常正常。真正值得关注的是 MemAvailable,它估算的是“在不触发明显内存回收的情况下还能分给新应用多少内存”。比如一台 8 GB 内存的手机,开机后 MemFree 只有几百 MB,看起来好像很紧张,但 MemAvailable 还能剩 3~4 GB,说明系统吃得很饱但很健康。反过来,如果 MemAvailable 经常掉到几百 MB 甚至几十 MB,那就意味着系统随时可能开始杀后台进程,这时候手机上那种“切应用就重载”的毛病就会频繁出现。
2.4 top:看一眼就被当场抓住的“内存大户”
实时监控内存,top 命令是不能跳过去的。在 Android 上跑 adb shell top,默认会输出所有进程的 CPU 和内存占用并持续刷新。相比桌面 Linux 的 top,Android 版本精简了一些字段,核心看这几个就可以:PID(进程号)、VSS(虚拟内存)、RSS(物理占用)、CPU 使用率、进程名。一般我习惯先执行 adb shell top -m 10 -n 1,只取占用最高的前 10 个进程、采样一次就退出。这样不会因为 top 本身一直刷新而干扰后续监控,也不会让 USB 传输通道被大量文本塞满。如果发现某个进程占用异常高,再用 pidof 包名拿到进程号,配合 dumpsys meminfo 做定向分析。
3. 实时监控:从“看瞬间”到“盯住整段过程”
3.1 用 watch 实现“每两秒刷新一次”
单独执行一次 dumpsys 或者 top,只能看某个瞬间的内存快照。可内存问题往往是“玩到某个步骤才突然暴涨”“杀后台之前有个先兆”这种动态过程,瞬间快照根本不够用。命令行里最简单的实时做法,是在电脑端用 watch 命令反复执行 adb shell:watch -n 2 "adb shell dumpsys meminfo com.zhihu.android | grep -E 'TOTAL|PSS'". 这里 -n 2 表示每两秒刷新一次输出,屏幕会像心电图一样跳动着更新。Windows 用户没有原生 watch,可以用一个简单的 for 循环替代:for /l %i in () do (adb shell dumpsys meminfo com.zhihu.android | findstr TOTAL & timeout /t 2),效果差不多。这种方案的好处是不需要手机端装任何 App,代价是每条命令都要走一遍 USB/ADB 链路,持续跑会略微增加手机 CPU 和电量消耗,所以采样间隔别太短,两秒以上比较合理。
3.2 为什么我建议你看 TOTAL 而不是某一行
在实时监控的场景下,屏幕输出越精简越好。dumpsys meminfo 加包名后输出仍然有几十行,人眼根本盯不过来。我的做法是:先确认包名对应的主进程名和若干子进程名(比如微信的 com.tencent.mm、com.tencent.mm:appbrand 等),然后用 grep 把 Total PSS by process 这一行,也就是总进程那一行提取出来。因为 Android 按包名管理进程,一个包可能对应多个进程(主进程加各种服务进程、渲染进程),把总内存加起来才是这个应用的真实占用量。忽略掉 Java Heap、Native Heap 这些细项,先判断总量涨跌趋势,等发现问题再停下来做详细拆分。
3.3 写一个能落地的监控小脚本
实时盯屏幕只适合短时间的“现场抓包”,如果要做长时间监测,比如记录一晚上待机内存曲线、或者压测一小时看内存有没有泄漏,敲键盘盯屏幕显然不现实。这时候就该写个脚本自动采样并存储了。下面这个 Bash 脚本是我平时最常用的模板,原理很简单:循环采样,把时间戳、系统可用内存、目标包名总 PSS 追加到 CSV 文件,最后你可以把它拖进 Excel 或者直接用 Python 画折线图。
#!/bin/bash # 用法: ./mem_monitor.sh com.tencent.mm 120 PACKAGE=$1 DURATION=${2:-120} INTERVAL=${3:-5} OUT_FILE="mem_$(date +%Y%m%d_%H%M%S).csv" echo "timestamp,total_pss_kb,mem_available_kb" > $OUT_FILE for ((i=0; i<$DURATION; i++)); do TS=$(date +%H:%M:%S) PSS=$(adb shell dumpsys meminfo $PACKAGE | grep -E "TOTAL PSS by process" | awk '{print $(NF-1)}') AVAIL=$(adb shell cat /proc/meminfo | grep MemAvailable | awk '{print $2}') echo "$TS,$PSS,$AVAIL" >> $OUT_FILE sleep $INTERVAL done echo "Done, saved to $OUT_FILE"把这个脚本保存为 mem_monitor.sh,赋予执行权限后运行,5 秒一条记录,默认跑 120 次,也就是 10 分钟左右的数据量。脚本里的 PSS 提取那一步用的是关键字 “TOTAL PSS by process”,因为 dumpsys meminfo 输出的 TOTAL 行依赖具体运行时版本,不同 Android 版本格式略有差异,而 “TOTAL PSS by process” 这个标题在不同版本里相对稳定。要是提取出来是空值,先手动跑一次命令看看你那台设备的输出标题是什么,再回头调整 grep 关键字。
3.4 采样间隔也别太贪心,USB 通道不是无限带宽
写监控脚本时最想贪的是把间隔压到 1 秒甚至更快,但实操下来不建议这么干。每次 dumpsys meminfo 执行本身要花几百毫秒到一秒多,如果 App 进程很多、设备又比较老,一条命令跑两秒都可能。间隔 1 秒的脚本实际上会一直占着 ADB 通道,其他命令根本没机会挤进来,而且频繁拉取内存数据本身会造成额外的 CPU 和 I/O 活动,样本就会失真。我实测在大多数中端机上,5 秒采样间隔已经能看清波动趋势,10 秒间隔适合长时间记录,1~2 秒只适合针对单个进程的短时 spike 追踪。另外,监控期间尽量不要在手机端做其他重操作,也别让电脑同时挂着游戏下载之类的占资源程序,以免动静混淆,回头分析数据的时候分不清曲线尖峰到底是 App 行为还是外部干扰。
3.5 无线 ADB 的实用场景与局限
有些场景下你用不了 USB 线,比如设备在车里、电视挂墙上、或者手机尾插已经接触不良,这时可以走无线 ADB。传统做法是先用 USB 连一次,adb tcpip 5555 打开无线端口,然后拔线,再用 adb connect 设备IP:5555 连接。新版 Android 11 以上还支持“无线调试”开关,开发者选项里直接显示 IP 地址和端口,配上配对码就能免 USB 完成连接。无线 ADB 做实时监控有个需要注意的点:延迟比 USB 高,而且 Wi-Fi 不稳定时采样命令可能超时。所以脚本里最好加上超时判断,比如 adb connect 后用 adb shell echo ok 探活,连不上就跳过本轮采样,避免脚本因为等待连接卡死产生一大段空白数据。我这个脚本就没加这层容错,属于够用就行的版本,你要是准备长时间挂机,建议自己套个 if 判断。
4. 常见问题与排查技巧实录
4.1 adb devices 不显示设备,从头到尾排查一遍
这个问题被问得最多,尤其是红米 K50、荣耀 90 这类机型,论坛里一搜一大把。按我踩坑的经验,排查顺序应该是:数据线(原装或支持数据传输的线,很多“充电线”只有电源没有数据)-> 手机端授权弹窗(看屏幕有没有“允许 USB 调试吗”,没弹就撤销授权重新插)-> USB 连接模式(切到文件传输/MTP)-> 电脑端驱动(设备管理器里有没有带感叹号的设备)-> 开发者选项里重新关闭再打开 USB 调试。还有一个特别常见的低级错误:有人进的是 fastboot 模式(音量下加电源键开机那个界面),然后问我“为什么 adb devices 看不见设备”,这当然看不见,fastboot 模式要用 fastboot devices 命令,和 ADB 走的完全不是同一个通道。红米 K50 的 fastboot 连电脑后,应该用的是 miflash 刷机工具或 fastboot 命令,不是 adb push 那套。
4.2 unauthorized 怎么处理,以及怎么撤销授权
如果 adb devices 里显示的是 unauthorized,说明电脑端发起了连接,但手机端没有批准。最直接的解决办法是:拔掉数据线,在手机“开发者选项”里找到“撤销 USB 调试授权”,全部撤销,然后重新插线,手机会再次弹出 RSA 指纹授权窗口,勾选“始终允许”即可。有时候弹窗被系统神隐了,可能是“ USB 安装”权限没开,或者厂商把调试授权弹窗默认折叠到通知栏里了,下拉通知栏找一找。另外,某些设备在开启“USB 调试(安全设置)”这一额外开关前只允许极少部分命令运行,真正要完全体 adb 操作,需要把这个开关也打开。
4.3 抓数据时发现“TOTAL 数字对不上”是为什么
dumpsys meminfo 加包名后的 TOTAL 和系统全局输出里的同名应用行加总经常对不上,原因主要是“进程归属”。有些进程虽然属于同一个包,但没有被包管理器统计在内,比如以 isolated 方式运行的各种渲染沙箱进程。当你发现某个 App 实际内存远大于包名查询结果时,试试 adb shell ps -A | grep 包名 看一下到底挂了多少个子进程,再逐个查。还有一种情况是 Kernel 层的内存和 GPU 显存不计入进程 PSS,而是挂在内核或其他系统进程下,这部分内存如果异常(比如显存泄漏),你在应用层 grep 完全看不出端倪,得靠 /proc/meminfo 里的字段和 GPU 相关的节点去排查。
4.4 电视、车机这些“非典型 Android 设备”的 ADB 坑
电视和车机的系统一般是 Android 的定制或者衍生版本,ADB 通道虽然保留了,但开启方式千奇百怪。老款创维电视,有的在“设置 -> 本机信息”里连按遥控器“上下左右”或者“确认键”若干次,有的需要进工厂菜单输密码,密码通常是万能四位数或者六位数,这类信息时效性很强,建议直接搜具体型号。车载 ADB 更特殊,很多车机虽然开在 Android 上,但通用开发者选项被隐藏了,需要按特定顺序操作(比如连点“系统信息”里的版本号多次)。需要提醒的是,车机内存监控会涉及车机系统的稳定性和驾驶安全,不建议在车辆行驶过程中操作。如果真的在车机上跑 ADB 监控,建议用无线方式连接,并把电脑放在安全位置,只做记录分析,不做实时交互。另外,电视、盒子、车机这些设备的系统分区通常是只读的,dumpsys meminfo 可以用,但类似 adb remount 改分区这种操作会失败,这是正常的,别以为是你设备连接出了问题。
4.5 抓文件时常见的 content:// 和 data 目录访问权限问题
监控内存除了看数字,很多时候还要配合抓取日志文件、导出 ANR 文件、拉取应用私有目录下的 trace 文件来分析。这类操作经常踩到一个坑:在 Android 11 以上,/sdcard/Android/data 目录被加上了访问限制,普通文件管理器进不去,很多 PC 端工具也拷贝不出来。但用 adb 一般是可以直接进的:adb shell ls /sdcard/Android/data/com.tencent.mm/ 通常能列出来。如果连 adb 也看不到,说明是厂商额外加固,可以尝试把文件复制到 /data/local/tmp 再 adb pull,这招在大多数场景下都好使。你在论坛或者网盘里看到别人贴的 content:// 开头的路径,其实是应用把私有目录通过 FileProvider 暴露给文件选择器的临时 URI,这个和 ADB 无关,但理解了它的存在,你就知道为什么有些 App 里能看到的文件直接用电脑资源管理器却翻不出来了。
4.6 动态校验码、厂商定制鉴权类问题怎么理解
前文提过小天才这类设备的 ADB 开启会要求校验码。实际工作中还可能遇到更复杂的“动态密码计算器”,这类工具的存在是因为部分厂商在出厂的工程固件里加入了动态密钥机制,每次开启调试时终端会打印一串随机数,需要拿到厂商算法里算一下才能继续。这类机制不是给普通用户设计的,遇到直接进开发者模式弹校验码的情况,一般看设备屏幕上的提示去厂商官方通道获取;遇到网上流传的各种“计算器”工具,我的建议是谨慎使用,因为不确定算法来源是否安全,与其冒险,不如查清楚这款设备的官方调试方案。手机圈里有一类很常见的软件叫 Scene、叫 vtools 系列,它的通用做法就是利用 ADB 赋予的权限执行 shell 脚本来实现对系统调度的精细化控制,比如通过 adb shell sh /storage/emulated/0/android/data/com.omarea.vtools/up.sh 这种方式拉起里面的配置脚本。这类工具本质上是在用 ADB 的能力做系统调校,和咱们今天说实时监控属于同一条技术路线的不同应用。
5. 进阶:把内存监控和日志抓取配合起来用
5.1 内存曲线是“果”,日志才是“因”
单纯盯着内存数字变化,有时候能看出“哪里不对”,但看不出“为什么不对”。比如你监控到某 App 的 PSS 内存像过山车一样忽上忽下,可能是应用自己在做缓存淘汰,也可能是因为系统开始杀后台导致它频繁重新初始化。这时候必须打开日志通道来验证。adb logcat 就是干这个的,它输出系统各模块的日志。和内存相关的重点看两个方向:一是 Java 层的 OutOfMemoryError 堆栈,这种一搜关键字就能定位到具体代码路径;二是低内存杀手(lmkd)和 ActivityManager 在日志里留下的进程被杀记录。
5.2 观察 lowmemorykiller 的“杀人现场”
Android 的 LMKD(Low Memory Killer Daemon)会在系统内存吃紧时选择性地杀进程,判断依据就是每个进程的 oom_adj 优先级和实际内存占用。启动 logcat 实时过滤:adb logcat -v time -b all | grep -E "lowmemorykiller|lmkd|am_kill|OutOfMemory",你会看到类似 “am_kill: 2345 com.example.app, adj 900, 45678 kb” 这样的记录。当你的监控脚本采样到的 MemAvailable 快速下降,同时日志里出现 am_kill / lowmemorykiller 记录,整个因果链就完整了。我经常遇到这种情况:用户反馈 App 总是闪退,看 dumpsys 也没看到明显异常,结果一抓日志发现是被 LMKD 杀的,不是 App 自己崩溃。这时要做的就不是修代码逻辑了,而是优化内存占用,降低在低内存场景下被优先杀掉的概率。
5.3 崩溃日志定向获取:logcat 的实用过滤技巧
抓崩溃日志的时候,有几种过滤姿势。最简单的是 adb logcat -b crash,只抓崩溃缓冲区的数据,里面是 Java 层崩溃和 tombstone 开始之前的核心堆栈。搭配时间戳和进程号过滤更精确:先 pidof 包名,然后 adb logcat --pid=进程号,配合 -s 加 tag 过滤,可以只看目标进程的报错。常见的内存相关关键字有 OutOfMemoryError、OOM、lmkd、am_kill、killing background、free memory。想保存到本地文件:adb logcat -d > crash.log,这条命令把当前缓冲区日志导出来然后退出,不会一直阻塞。要做持续监控,去点 -d 参数让它流式输出,在电脑端重定向到文件就行。
5.4 一个能同时记录内存和日志的“组合拳”脚本
如果想把采样和日志一次性搞定,可以按这个思路写脚本:第一个终端或后台任务跑内存采样循环,第二个终端跑 logcat 筛选并写入文件,等测试场景跑完以后,用时间戳把内存曲线和日志事件对到同一时间轴。下面是一个实用的 logcat 抓取命令示例,不需要额外脚本文件:
adb logcat -v time -b all | grep --line-buffered -E "lmkd|am_kill|OutOfMemory|lowmemorykiller" >> fatal.log这样跑三个小时都不担心内存炸掉,因为 grep 已经过滤了大部分无关输出。跑完之后把 fatal.log 里的事件按时间列出来,再对照 CSV 曲线,基本能找到内存暴涨的触发时刻和系统出手的临界点。
5.5 监控工具再多,不如先看原生数据
现在市面上的监控工具很多,Android Studio 自带的 Profiler、Perfetto 都能做得很好看,也的确适合深度分析。但就“实时监控 Android 内存”这个需求来说,ADB 命令行方案的最大优势是轻量、直接、可控:不装额外组件、不依赖 IDE、脚本可以随时改随时跑,连电视车机这类没有图形化 Profiler 的环境都能用。我见过不少人用尽了各路工具还找不到内存问题,最后退回到 dumpsys meminfo + logcat 的原始组合,反而一下定位到了问题。所以我始终建议先把原生命令用熟,再看那些工具,你才知道工具展示的数据是从哪儿来的、哪些字段可信、哪些只是估算。
我个人在实际操作中的体会是,ADB 监控内存这件事,真正考验人的不是命令背得熟不熟,而是能不能把“进程 PSS、系统可用内存、lmkd 杀进程日志”这三层信息串联起来讲出一个完整的故事。刚开始我是用 Windows 下写批处理循环、采样间隔调到 1 秒,结果数据一堆却看不出所以然;后来改成 5 秒间隔、只记录 TOTAL PSS 和 MemAvailable,再搭配 logcat 抓关键事件,反而一眼就能定位问题。这套组合我后来一直保留下来了,每台新手机拿回来都会先跑一遍,算是给设备建立个内存底账。你如果也有类似的内存困扰,不妨从今天这些命令开始,先把一分钟快照跑通,再决定要不要上长时间自动化记录。最后再分享一个小技巧:采样脚本里记录时间戳的时候,最好精确到秒,并且和 logcat 的 -v time 输出保持同一时区,后面做数据比对时会省很多麻烦。