4. CPU竞争分析:cpuinfo、systrace、perfetto中的线程状态

现象识别 —— 当ANR发生时,往往伴随着CPU竞争激烈,表现为:主线程长时间处于Runnable状态却得不到调度、系统负载居高不下、关键线程频繁被抢占。最终用户感知到界面卡死或“应用无响应”对话框。

4.1 现象识别:CPU竞争导致ANR的典型信号

CPU竞争型ANR的典型特征如下表所示,可用于初步定界:

观察维度典型信号怀疑程度
/proc/stat 中的 CPU 使用率user+system 持续 > 90%,iowait 低高
top 或 cpuinfo 负载load average 接近或超过 CPU 核心数高
systrace 主线程状态主线程长时间处于 Running(蓝色),但无进展极高
perfetto 调度记录同一时间多个高优先级线程争抢核心极高
logcat “ActivityManager: ANR” 前后CPU 占用突增,其他进程频繁唤醒中
注意:不要将 iowait 高(磁盘I/O瓶颈)误判为CPU竞争。iowait高时,CPU空闲等待I/O,而非被其他线程抢占。

4.2 数据采集:三工具并行取证

三种工具各有侧重,必须联合使用才能完整还原CPU竞争全景。

4.2.1 cpuinfo 快速快照

在ANR发生瞬间,通过 adb shell cat /proc/stat 和 top 获取全局负载:

adb shell cat /proc/stat | head -3
adb shell top -n 1 -d 1 | head -20

关键指标:
– cpuN 行:user/nice/system/idle/iowait/irq/softirq 比例。
– load average:1分钟、5分钟、15分钟。若1分钟 > 核心数 × 0.7,CPU竞争风险高。

4.2.2 systrace 线程状态染色

使用 atrace --async_start -a <pkg> sched freq idle 捕获,然后通过 adb shell atrace --async_stop -z > trace.ctrace 导出。打开后关注:

  • 主线程(UI Thread):颜色应为绿色(Sleeping)或蓝色(Running)?若长时间蓝色且无任何系统调用(syscall计数停顿),说明被更高优先级线程抢占。
  • 其他线程:查看是否有后台线程(如渲染线程、Binder线程、自定义线程池)频繁运行,导致核心满载。
  • 调度片段(Sched Fragment):查看每个线程的CPU占用率,识别“吃CPU大户”。

4.2.3 Perfetto 深度调度分析

Perfetto 提供了比 systrace 更细的调度视图。使用以下配置捕获:

adb shell perfetto \
  -c - -o /data/misc/perfetto-traces/trace.perfetto \
  <

Perfetto 界面中可看到:

  • 线程状态时间线:每个线程的 Running(绿色)、Runnable(可运行但未调度,蓝色)、Sleeping(灰色)、Uninterruptible Sleep(红色,I/O阻塞)。
  • CPU片分配:哪些线程在哪段时间内分配了哪个核心。
  • 全局调度延迟:Runnable 至 Running 的等待时间,如果多次超过 10ms 即为异常。

4.3 原因定位:故障树分析法

使用故障树从现象逐层深入,如下SVG所示:

CPU竞争故障树分析 ANR CPU竞争 频繁上下文切换 高优先级抢占 内核态死循环 大量Binder线程唤醒 后台线程优先级 > 0 驱动/系统调用死锁 方法:systrace查看线程 唤醒频率 > 100次/秒 方法:cpuinfo查看nice值 后台线程nice ≤ 0 方法:perfetto查看 Uninterruptible Sleep 状态 故障树使用原则:从“ANR CPU竞争”出发, 依次排查三个子分支,结合工具数据剪枝,快速定位根因。

4.4 修复动作:针对根因的精准干预

根据故障树分析结果,执行对应修复方案:

根因修复动作参考代码/配置示例
频繁上下文切换(大量Binder线程)减少不必要的Binder调用;合并跨进程请求;使用异步回调
// 合并IPC
List<Data> batch = batcher.flush();
mService.batchInsert(batch);
高优先级抢占(后台线程优先级过高)将后台线程优先级降至 THREAD_PRIORITY_BACKGROUND 或更低
new Thread(() -> {
    android.os.Process.
        setThreadPriority(
            Process.THREAD_PRIORITY_BACKGROUND);
    // ...
}).start();
内核态死循环检查驱动、HAL层、JNI实现;避免在持有锁的情况下执行耗时syscall
// 避免在 synchronized 块内调用
// Thread.sleep(long) 或 Binder#transact
多个线程争抢同一个锁(非明确分支但常见)降低锁粒度;改用读写锁或无锁数据结构
ReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
提示:修复后不要仅依赖标准测试,需构造CPU高负载场景(比如同时执行动画、网络请求、数据库写入)复现,确认主线程延迟不再出现。

4.5 复测:验证修复有效性

复测用例至少包含以下场景:

  1. 基线测试:修复前回放ANR现场(需保存原始trace),确认症状一致。
  2. 修复后测试:使用相同负载脚本运行,触发主线程逻辑(如点击、滑动、刷新)。
  3. 压力测试:使用 stressapptest 或 cyclictest 提高系统CPU负载至90%以上,再次检查主线程调度延迟。
  4. 工具验证:用 systrace 或 perfetto 捕获修复后trace,确认主线程不再长时间处于Running但无进展,后台线程的CPU占用率下降。

复测判定标准:

  • 主线程Runnable等待调度时间 < 2ms(原 > 50ms)。
  • 系统load average 在高峰时不超过核心数 × 0.5。
  • 无ANR日志生成。

4.6 预防:构建CPU竞争监控体系

将排查经验转化为预防机制:

预防措施实现方式监控指标
运行时线程优先级审计在WebView、三方SDK初始化后遍历ThreadGroup,检查优先级非系统线程priority < -2 则记录警告
Binder调用频次限制封装Binder助手,内置节流阀(每100ms最多5次)调用次数/秒 > 阈值时降级
后台任务CPU限额使用WorkManager设置setExpedited(false),限制CPU密集型任务CPU使用率 > 30%持续10秒时暂停
线上perfetto自动捕获集成Perfetto SDK,当主线程调度延迟>100ms时自动dump tracetrace上报率 > 95%
总结:CPU竞争型ANR的排查核心在于“看调度”——通过cpuinfo快速感知整体负载,通过systrace捕获线程状态变化,通过perfetto精确计算调度延误。结合故障树剪枝,可高效定位到频繁上下文切换、高优先级抢占或内核态异常。预防阶段需将检查和节流机制内建到应用运行时,防患于未然。