Android稳定性-40-Android 内存稳定性分析:PSS、RSS、USS、Java Heap 和 Native Heap

PSS、RSS、USS、Java Heap 和 Native Heap 是 Android 稳定性测试里非常容易被写成一句话的问题,但真正能推动开发定位的材料,必须包含现象、时间、系统状态、关键日志、复现路径和排除项。本文围绕 内存 建立一条可执行的排查链路:先判断问题类型,再采集正确证据,然后把证据按时间线组织成结论。

这篇不是命令清单,而是面向测试开发的定位方法。你应该能在读完后知道工具在哪里、每个输出看什么、哪些结论不能轻易下,以及怎样把一次偶现问题整理成开发可以继续分析的报告。

一、先定义 内存 问题的边界

很多定位失败不是工具不会用,而是一开始没有定义边界。遇到 PSS、RSS、USS、Java Heap 和 Native Heap 时,先把它拆成可验证的问题:是否发生在前台应用、是否影响整个系统、adb 是否在线、是否发生重启、是否能复现、是否和长稳时长相关。边界越清楚,后面的日志越少走弯路。

内存 类问题,不要直接写“系统不稳定”。至少要写出发生时间、持续时间、用户可见现象、设备连接状态、任务类型、恢复方式,以及问题前后有没有 crash、ANR、watchdog、panic、资源上涨或服务重启。

二、定位工具在系统里的位置

这类问题通常跨越 App、Framework、Native Service、HAL 和 Kernel。工具也要分层使用:logcat 负责过程日志,bugreport 负责系统快照,dropbox 负责异常索引,dumpsys 负责服务状态,Perfetto 负责时间线,procfs 和 pstore 负责底层现场。

层级 工具 看什么 典型输出
App/Framework logcat、dropbox、traces crash、ANR、主线程和 binder logcat_all.txt、anr traces
System Server dumpsys、event log Activity、Window、Power、Display 状态 dumpsys_window.txt
Native tombstone、Perfetto native crash、调度、合成、binder tombstone_XX、pftrace
Kernel pstore、dmesg、procfs panic、资源、调度和驱动错误 console-ramoops、proc 文件
平台 自动化报告、录屏 触发动作和用户视角 任务日志、截图、视频

三、第一轮采集命令

第一轮采集的目标是固定现场,不是立刻证明根因。命令应当在问题发生后尽快执行,并把输出保存到以设备、时间、任务命名的目录。

1
2
3
4
5
6
7
8
9
adb devices
adb shell date; adb shell uptime
adb logcat -b main -b system -b events -b crash -v threadtime -d > logcat_all.txt
adb bugreport ./bugreport.zip
adb shell dumpsys meminfo -a > meminfo_all.txt
adb shell dumpsys meminfo com.example.app > meminfo_app.txt
adb shell cat /proc/meminfo > proc_meminfo.txt
adb shell cat /proc/pressure/memory > psi_memory.txt
adb shell dumpsys activity lmk > lmk.txt

如果设备正在卡住,优先采集不会改变现场的命令。比如先 dateuptimedumpsyslogcat -d,再考虑按键唤醒、重启设备或重新启动服务。任何会改变状态的动作都要记录在时间线里。

四、关键日志入口和关键词

排查 内存 时,日志搜索要围绕事件链,而不是只搜 error。建议先搜时间窗口,再搜关键词。相关关键词包括:PSS, RSS, USS, Java Heap, Native Heap, LMKD。还要结合 am_wm_powerdisplaybinderwatchdoglmkdtombstonepanic 等系统词。

1
2
3
rg -n "watchdog|ANR|am_anr|am_crash|binder|timeout|killing|lowmemory|panic|reboot|SurfaceFlinger" logcat_all.txt
rg -n "Window|Display|Power|ActivityManager|InputDispatcher|lmkd|sched|blocked" bugreport.txt
rg -n "PSS|LMKD" *.txt

关键词只是入口。看到命中后要回看前后 1 到 5 分钟,确认它是原因、结果还是无关噪声。

五、时间线怎么搭

稳定性问题必须按时间线写。建议建立四列:平台动作、用户可见现象、系统日志、系统状态。每个证据都落到秒级时间,无法确定时写明来源。

时间 平台/用户现象 系统证据 判断
T-300s 任务正常运行 资源指标开始抬升或日志出现 timeout 可能是前兆
T-30s 页面开始变慢 event log 出现 input 或 binder 延迟 进入异常窗口
T 问题可见 dropbox/dumpsys/perfetto 命中 核心现场
T+60s 恢复或重启 服务重启、LMKD、boot reason 判断恢复路径
T+300s 二次采集 bugreport、pstore、tombstone 补充证据

六、PSS、RSS、USS、Java Heap 和 Native Heap 的判断口径

PSS 适合做 Android 进程整体内存趋势,因为共享页按比例分摊;RSS 容易被共享库和映射影响,适合结合 smaps 看页类型;USS 表示独占页,更接近杀掉进程后可释放的收益;Java Heap 对应 ART/Dalvik 对象;Native Heap 对应 malloc、JNI、C/C++ 库和部分 bitmap/native buffer。

长稳分析不要只看单点最大值,要看斜率、场景边界和 GC/回收后是否下降。页面反复进入后 PSS 台阶式上涨,且退出后不回落,才像泄漏。Native Heap 涨但 Java Heap 稳定,要优先看 JNI、bitmap、media、skia、OpenGL、ashmem 或 vendor native 库。

七、如何判断根因方向

内存 类问题,根因方向通常不是单点日志,而是一组证据共同指向。比如前台应用 ANR 同时出现 system_server binder 阻塞,不能直接归因到应用;CPU 高但 Perfetto 显示关键线程在 uninterruptible sleep,不能简单说是计算耗时;黑屏时 Power 状态是 ON、Display 是 ON、SurfaceFlinger 没有合成,则优先看显示链路。

判断根因时先问三个问题:异常是否发生在问题前,异常是否能解释用户现象,异常是否有独立证据支持。如果三个问题有一个回答不上来,就只能写“相关线索”,不能写“根因”。

八、表格化对比:现象、证据和下一步

现象 优先证据 支持判断 下一步
局部应用异常 app crash、app ANR、进程状态 只影响单包,系统服务正常 交给应用并附主线程栈
系统服务阻塞 watchdog、dumpsys、traces system_server 关键线程等待 看锁、binder、CPU、对端服务
Native/HAL 异常 tombstone、Perfetto、HAL 日志 native backtrace 或 HAL 超时 符号化并找模块 owner
Kernel/驱动异常 pstore、dmesg、bootreason panic、oops、硬件复位 交 kernel/vendor 并保留原始 pstore
资源耗尽 meminfo、fd、thread、binder、lmkd 指标持续上涨并触发失败 做趋势图和最小复现

九、完整案例

背景:一台 userdebug 设备执行长稳任务,任务包含 Monkey、业务页面巡检和定时截图。第 7 小时平台记录到异常,现象属于 PSS、RSS、USS、Java Heap 和 Native Heap。adb 仍能连接,但用户侧体验异常持续约 90 秒。

处理过程:先保存 dateuptime 和 bugreport,确认没有人为重启;再按问题时间窗口提取 logcat、dropbox 和 dumpsys。日志显示异常前 2 分钟已经出现 PSS 相关告警,问题发生时 LMKD 相关状态异常。Perfetto 或系统快照进一步证明关键线程没有正常推进。最后根据证据排除脚本误触、网络波动和单一应用 crash,把问题收敛到对应系统链路。

结论写法:本次问题不是“偶现卡住”这种泛描述,而是“在某任务压力下,某链路在 T 到 T+90s 之间停止推进,导致用户可见异常;恢复后系统未重启,核心证据见附件 A/B/C”。

十、常见误判

第一,只看最后一条 error。最后打印的日志经常是结果,不是原因。第二,把平台脚本异常当系统异常,或者把系统异常推给脚本。两者要靠设备状态和用户可见现象区分。第三,只用截图证明问题。截图证明现象,不证明链路。第四,不保留原始文件,只摘录几行。开发无法复核时,结论可信度会明显下降。第五,忽略版本差异。user、userdebug、eng 的日志权限和服务输出不同,同一命令在不同版本可见内容不一样。

十一、复现和二次采集策略

如果问题能复现,要把第一轮被动采集升级为主动采集。复现前开启循环 logcat、定时 dumpsys、关键指标采样和 Perfetto 触发条件。长稳任务建议每 30 到 60 秒采集一次轻量指标,每次异常立即抓重型快照。

1
2
3
4
5
6
7
while true; do
adb shell date >> heartbeat.txt
adb shell uptime >> heartbeat.txt
adb shell dumpsys meminfo com.example.app >> meminfo_loop.txt
adb shell top -b -n 1 -H | head -80 >> top_loop.txt
sleep 30
done

循环采集要控制体积,避免日志本身把设备拖慢。关键是让指标能覆盖异常前的斜率,而不是只拿到异常后的静态值。

十二、检查清单

  • 是否明确问题类型、发生时间、持续时间和恢复方式
  • 是否确认 adb 在线状态、boot id、uptime 和 firstboot
  • 是否保存完整 logcat、bugreport、dropbox 或对应底层文件
  • 是否按时间窗口提取关键日志,而不是只搜 error
  • 是否把 PSS, RSS, USS 等核心证据串成时间线
  • 是否区分根因、触发条件、伴随现象和恢复动作
  • 是否排除脚本误触、网络异常、设备离线、手动操作
  • 是否保留原始附件路径和命令版本
  • 是否写出未确认项和下一步需要的 owner
  • 是否提供可复现任务、参数和环境信息

十三、输出物模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
问题标题:PSS、RSS、USS、Java Heap 和 Native Heap - 设备/版本/任务/发生时间
设备信息:model、serial、build、Android version、root/userdebug 状态
任务信息:脚本版本、参数、开始时间、压力类型
现象:用户可见表现、持续时间、恢复方式、是否重启
时间校准:宿主机时间、设备时间、差值、校准方法

关键证据:
1. logcat:文件名、时间窗口、关键行号
2. bugreport/dumpsys:服务状态、关键字段
3. dropbox/traces/tombstone/pstore:异常索引和展开证据
4. Perfetto/指标:线程、CPU、内存、I/O 或显示时间线

初步判断:基于证据链描述,不写超出证据的结论
已排除:脚本误触、手动重启、单应用 crash、网络波动等
待确认:需要哪个模块 owner 继续分析,以及还缺什么日志
附件:原始文件路径和采集命令

十四、提单时怎么写才有效

有效提单不追求把所有日志贴满正文,而是让读者 3 分钟内理解问题链路。正文写结论和证据摘要,附件放原始文件。关键日志要带文件名、时间点和检索关键词。对 内存 问题,要明确你判断的是“疑似方向”还是“已由多项证据支持的方向”。

如果证据不足,也要诚实说明。比如“未抓到异常窗口 Perfetto,当前只能根据 dropbox 和 logcat 判断 system_server 可能被阻塞”。这种写法比强行下结论更容易推进,因为它告诉开发下一轮需要补什么。

十五、小结

Android 内存稳定性 的核心不是记命令,而是建立证据链。先定义现象,再固定时间窗口,然后按层采集:logcat 看过程,dumpsys 看状态,dropbox 看异常索引,Perfetto 看时间线,procfs/pstore 看底层现场。最后用表格、案例、检查清单和模板把结论变成可复核的工程材料。

稳定性问题通常不会因为一条日志就被解决,但一条组织良好的排查链路,能显著减少争论,把“偶现不好查”推进到“下一步由哪个模块验证”。

十六、采集目录和命名规范

内存稳定性 问题最怕多轮采集后文件混在一起。建议目录使用 serial_build_task_time 命名,下面固定放 00_meta01_raw02_filtered03_trace04_report00_meta 保存设备型号、系统版本、任务参数、开始时间、问题时间和操作者动作。01_raw 保存原始 bugreport、logcat、dumpsys、pstore 或 trace,任何过滤结果都不要覆盖原始文件。02_filtered 保存按时间窗口裁剪出来的关键片段。03_trace 放 Perfetto、systrace、录屏和截图。04_report 放时间线、初步结论和待确认项。

文件命名要能从名字看出来源和时间,例如 logcat_20260325_021000_022000_threadtime.txtdumpsys_window_T_plus_10s.txtperfetto_black_screen_30s.pftrace。这样开发拿到附件后不需要反复询问“这份日志是什么时候抓的”。

十七、时间校准方法

内存稳定性 分析必须说明时间如何校准。自动化平台记录的是宿主机时间,Android 日志记录的是设备时间,bugreport 里还可能混有 elapsed realtime、uptime 和 wall clock。稳定性报告里至少写三项:宿主机当前时间、设备 date 输出、设备 uptime 输出。发生重启时,再补 ro.runtime.firstbootsys.boot_completed 和 boot id。

如果问题发生在凌晨或设备离线恢复后,时间漂移更常见。不要只写“约 2 点”。更可靠的写法是:“平台记录 02:14:30,设备 date 与宿主机差 +3 秒;logcat 以设备时间为准”。如果无法校准,也要写明无法校准,并扩大日志窗口。

十八、自动化平台应该提前埋哪些点

事后定位依赖现场,但现场往往在问题发生后消失。因此平台侧要提前记录心跳:每分钟保存 dateuptime、adb 状态、前台包名、屏幕状态、关键进程 pid、top 摘要和最近 200 行事件日志。对长稳任务,还应保存任务动作流水,例如点击了哪个页面、执行了哪个 shell 命令、是否发生过 adb reconnect。

这些信息看起来零碎,但在 内存稳定性 问题里经常决定方向。比如黑屏时如果前台包名已切到 Launcher,说明可能不是原应用页面;重启后 boot id 变化能证明确实重启;CPU 高之前动作流水显示正在批量扫描媒体,就能把复现范围缩小到媒体服务链路。

十九、如何做最小复现

最小复现不是把长稳任务原样跑 12 小时,而是从时间线里找触发条件。先看异常前 5 到 15 分钟发生了哪些重复动作,再把动作拆成可控变量:应用页面、网络状态、亮灭屏、插拔电、后台切换、压力参数、温度、内存水位和并发任务。每次只改变一个变量,记录 内存稳定性 是否复现。

如果问题只在长时间后出现,要保留“预热阶段”和“触发阶段”。例如先循环进入页面 300 次制造资源累积,再单独执行一次唤醒或切换动作触发异常。这样比直接跑整套任务更容易让开发在本地复现,也方便验证修复是否有效。

二十、如何区分根因、诱因和后果

稳定性报告里常把三者混在一起。根因是直接导致系统进入异常状态的缺陷,诱因是触发缺陷的外部条件,后果是系统异常后的连锁表现。内存稳定性 问题尤其需要拆开写。例如 CPU 高可能是根因,也可能是异常恢复时的后果;ANR 可能是应用主线程问题,也可能是 system_server 已经阻塞后的表现;重启可能是 watchdog 主动拉起,也可能是 kernel panic 后的结果。

判断标准是时间和因果:先发生且能解释后续现象的证据更接近根因;只在异常后出现的日志通常是后果;改变压力参数后复现概率变化的因素多半是诱因。报告中用“根因证据”“诱因条件”“伴随现象”分段,会比一句“疑似某模块问题”更有用。

二十一、版本差异和权限限制

不同 Android 版本、不同厂商 user 版本对日志权限限制不同。/data/anr/data/tombstones/data/system/dropbox/sys/fs/pstore、binder debugfs 等路径在 user 版本上可能不可读。命令失败不代表没有证据,可能需要 bugreport、厂商诊断包、userdebug 复现或工程开关。

因此提单时要把命令失败也记录下来:命令、返回值、权限错误、设备版本。开发看到权限限制后,可以判断是否需要提供 eng/userdebug 包或打开额外日志。不要为了拿日志随意 root、remount 或清数据,因为这些动作可能改变复现条件。

二十二、证据摘录的尺度

正文摘录要短而准。每类证据摘 3 到 10 行关键内容即可,但必须附上原始文件名和行号或时间点。内存稳定性 问题常见的有效摘录包括:异常 tag、reason、blocked thread、pid/tid、调用栈顶部、boot reason、资源数值、线程状态、Perfetto 时间范围。不要把几百行日志直接贴到正文里,读者会失去主线。

摘录时保留原始大小写和关键字段,不要凭印象改写。比如 Input dispatching timed outsystem_server_watchdogKernel panic - not syncingToo many open files 这些字符串本身就是检索入口。中文解释可以放在摘录下面。

二十三、跨团队交接方式

内存稳定性 往往需要应用、Framework、性能、内核、显示、厂商 HAL 多个 owner 协作。测试侧交接时不要只按组织结构派单,而要按证据指向派单。应用 owner 需要包名、版本、操作路径和应用栈;Framework owner 需要 system_server traces、dumpsys 和 event log;Kernel/vendor owner 需要 pstore、dmesg、tombstone、符号版本和硬件批次。

如果证据跨层,建议在报告开头写“当前优先 owner”和“需要协同 owner”。例如“优先显示 HAL,协同 Framework Window/Display”,比同时抄送所有团队更有效。每个 owner 看到自己需要看的附件,推进速度会快很多。

二十四、回归验证怎么设计

修复后不能只跑一次原场景。要验证三个层面:第一,原始复现路径是否不再触发;第二,关键指标是否恢复到基线,例如 内存稳定性 相关计数、延迟、资源曲线或异常 tag 是否消失;第三,长稳压力下是否没有新的连锁问题。回归报告也要保留同样的采集目录,便于和修复前对比。

如果原问题是偶现,要用概率表达结果,例如“修复前 5/20 次复现,修复后同条件 0/50 次复现,最长连续运行 24 小时”。这比“未复现”更有工程意义。若仍然出现相似现象,要确认是否同一根因,不要自动复用旧结论。

二十五、最终小结

内存稳定性 的排查价值不在于某个单独命令,而在于把现场变成可复核的证据链。测试开发要做到三件事:第一,问题发生时尽快固定现场;第二,按时间线把日志、状态、trace 和平台动作串起来;第三,明确哪些是已证实结论,哪些只是下一步线索。

当报告能同时包含具体背景、工具位置、命令入口、表格、案例、误判、检查清单和输出模板时,问题就不再是“偶现不好查”,而是一个可以被分层分析、跨团队交接和回归验证的工程任务。

二十六、异常分级和优先级判断

内存稳定性 问题进入缺陷系统前,建议先做分级。影响单个三方应用且可恢复的问题,可以按普通应用稳定性处理;影响系统导航、锁屏、输入、显示、电源、电话、网络等核心能力的问题,应提高优先级;出现自动重启、数据丢失、无法恢复、反复 watchdog、kernel panic 或资源耗尽导致系统级不可用时,应按高优先级推进。

分级不是为了夸大问题,而是为了匹配响应速度和日志要求。高优先级问题需要保存更完整的现场,包括原始 bugreport、trace、pstore、录屏和自动化动作流水;普通问题则可以先提供最小复现和关键日志。分级依据要写在报告中,例如“影响整机输入,adb 在线但屏幕 90 秒无响应,因此按系统级卡死处理”。

二十七、趋势采样比单点截图更重要

长稳问题往往不是瞬间发生,而是指标逐步偏离。对 内存稳定性,单点截图只能说明某一刻异常,趋势采样才能说明异常如何形成。建议把关键指标按固定周期落盘:CPU、内存、FD、线程数、binder 调用、前台窗口、屏幕状态、温度、电量、boot id 和任务阶段。采样间隔要根据问题类型选择,资源泄漏可以 30 到 60 秒,卡顿黑屏可以 1 到 5 秒,重启类问题至少要有心跳记录。

趋势图不一定要复杂,CSV 加简单折线就能说明很多问题。比如 FD 每次进入页面增加 8 个且退出不下降,线程数每轮增加 1 个,PSS 每 30 分钟台阶式上涨,CPU 高峰与掉帧窗口完全重叠,这些都比“看起来变高了”更有说服力。趋势采样也能帮助排除误判:如果异常前指标长期稳定,根因可能不是资源累积。

二十八、附件验收标准

提交 内存稳定性 问题前,自己先按附件验收一遍。第一,原始文件能打开,压缩包没有损坏。第二,文件名能看出设备、时间和来源。第三,关键证据在正文中有索引,开发不需要从头翻完整 bugreport。第四,隐私或账号信息已按团队规则脱敏,但没有破坏关键字段。第五,所有命令输出都来自同一台设备和同一次复现,不能把不同轮次的日志混成一个结论。

如果必须混用多轮日志,要明确标注“复现 A”“复现 B”。例如第一轮只有 dropbox,第二轮补到了 Perfetto,就不能把第二轮 Perfetto 当成第一轮的直接证据,只能写“同路径二次复现补充证明”。这种严谨性会减少后续争论。

二十九、报告口径示例

推荐报告口径是“事实、证据、判断、缺口”四段式。事实描述用户可见现象和发生条件;证据列出关键日志、状态和 trace;判断只写证据能支持的方向;缺口说明还需要谁继续分析。以 内存稳定性 为例,不要写“系统有 bug 导致异常”,而要写“在某任务压力下,T 时刻出现某系统状态异常,与用户可见现象时间一致,当前证据指向某链路;尚缺少某层日志确认具体函数或驱动返回”。

这种口径看起来克制,但更容易被接受。它既没有把测试侧无法证明的内容说死,也没有把问题扔给开发自行猜测。稳定性测试的专业性,恰恰体现在能把不完整现场整理成边界清晰、方向明确、下一步可执行的材料。

三十、基线对比和环境控制

分析 内存稳定性 时要有基线。基线可以来自同版本正常设备、同设备问题前的采样、上一版稳定构建或关闭压力项后的对照结果。没有基线时,很多数字无法解释:线程 180 个可能正常也可能异常,PSS 900MB 可能是业务需要也可能是泄漏,CPU 60% 可能是前台动画也可能是死循环。报告里写清楚基线来源,能让结论更稳。

环境控制同样重要。电源、温度、网络、账号、SIM 卡、屏幕亮度、日志开关、Monkey seed、任务并发数都会影响稳定性表现。复现和回归时尽量保持一致;必须改变时,要在记录里说明。否则一次“修复后不复现”可能只是温度下降或压力减小带来的假象。

三十一、给开发的最短阅读路径

最后给开发留一条最短阅读路径:先看 summary.md 的 10 行摘要,再看 timeline.md 的核心时间线,然后按正文索引打开 3 到 5 个关键附件。摘要里写清楚“现象是什么、证据指向哪里、已排除什么、需要谁继续看”。对于 内存稳定性,建议在摘要末尾附上一个明确请求,例如“请 Display HAL owner 确认 T 时刻 present fence timeout 的返回路径”或“请 Framework owner 确认 system_server android.display 线程等待的 binder 对端”。

这条路径能减少沟通成本。稳定性问题常常附件很多,如果没有阅读顺序,开发会先看到噪声;如果阅读顺序清楚,开发能快速进入关键上下文。测试开发的交付物不只是日志集合,而是经过筛选、排序和解释的工程证据包。