一、这个火焰图/调用栈告诉我们什么com.example.Worker.run Self3000ms └─ java.lang.Object.wait Self2980ms ⬅ 大头在这表面现象:Worker.run方法执行了 3 秒,其中2980ms 消耗在Object.wait()上。关键结论:这不是性能问题,是线程在等待。二、Self Time 是什么(重要前提)在性能分析工具(如 Android Studio Profiler、Perfetto、Async Profiler)中:概念含义Total Time该方法及其所有子方法的总耗时Self Time只在这个方法自身代码里花的时间(不含子调用)举例:foo() Total100ms, Self10ms └─ bar() Total90ms, Self90ms说明foo主要在调用bar,自己只干了 10ms 的活。回到场景:Object.wait的 Self2980ms 意味着线程卡在wait()这一行 2.98 秒。三、这不是CPU 忙,而是线程阻塞关键区分:CPU 时间 vs Wall Clock 时间类型含义举例CPU Time(On-CPU)线程真正占用 CPU 执行指令的时间计算、循环Wall Time(Off-CPU)挂钟时间,包含等待wait/sleep/IO/锁Object.wait()期间:线程状态从RUNNABLE→WAITING/TIMED_WAITING线程被挂起,让出 CPUCPU 占用率是 0但挂钟时间在流逝使用工具时的陷阱如果你的 Profiler 是Sample模式(采样): → 显示的是 Wall Clock 时间 → 会看到 wait 的耗时 如果是CPU模式(只算 On-CPU): → wait/sleep 根本不会出现 → 显示没在干活判断你看到的是哪种:如果 wait/sleep 出现在火焰图里且 Self 很大 → 这是Wall Clock / Instrumentation模式。四、Object.wait()与Thread.sleep()的区别虽然都表现为线程不干活,但两者本质不同:特性Object.wait()Thread.sleep()释放锁✅ 释放当前持有的 monitor❌ 不释放锁唤醒方式被notify()/notifyAll()唤醒到时间自动唤醒必须在同步块内✅ 是❌ 否线程状态WAITING / TIMED_WAITINGTIMED_WAITING用途线程间协作(生产者-消费者)定时延迟可被 interrupt✅ 是✅ 是代码对比:// wait - 释放锁,等待通知synchronized(lock){while(!condition){lock.wait();// 阻塞在这里,锁被释放}}// sleep - 不释放锁,单纯延迟Thread.sleep(3000);// 阻塞 3 秒五、如何判断该不该关心这个 Self Time✅ 正常情况(不用管)场景 A:线程池的 Worker 空闲等待任务publicvoidrun(){while(running){Tasktaskqueue.take();// 底层是 waittask.execute();}} 空闲 Worker 卡在take()(内部 wait)是正常的,说明线程池够用。场景 B:HandlerThread 消息循环Looper.loop();└─MessageQueue.next()└─ nativePollOnce// 等消息 Handler 线程没消息时挂起,这就是设计。场景 C:主线程 idleandroid.os.MessageQueue.nativePollOnceSelf大量 主线程没事干时等待事件,正常状态。❌ 有问题的情况(需要排查)问题 1:主线程被 wait 卡住 → ANR 元凶main thread └─ MyActivity.onClick └─ Object.wait Self5000ms ⚠️ 主线程等 5 秒 → 用户点击后界面卡死 →必然 ANR问题 2:关键业务线程被 sleep 拖慢publicvoidloadData(){for(inti0;i100;i){Thread.sleep(100);// 无脑轮询 → 累计 10 秒if(checkReady())return;}} 应该改成事件通知 / CountDownLatch问题 3:锁竞争严重,大量线程 wait30 个线程同时: └─ SharedResource.doWork └─ Object.wait ⚠️ (等 monitor) 锁粒度太粗,需要拆分锁或用无锁结构问题 4:错误的线程通信模式// 忙等待(反模式)while(!done){Thread.sleep(10);// 轮询} 应该用wait/notify、CountDownLatch、Future六、如何进一步定位步骤 1:确认是哪种线程# 抓 Java 线程栈,看 Worker 线程是什么角色adb shellkill -3$(pidof com.example.app)adb pull /data/anr/traces.txt# 或者用 jstack (需要 JDK 工具链)jstackpid看栈的顶部:Worker-1 prio5 tid0x... nid0x... in Object.wait() java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on 0x... (a java.util.LinkedList) at java.lang.Object.wait(Object.java:502) at com.example.Queue.take(Queue.java:42) ⬅ 谁在调 wait at com.example.Worker.run(Worker.java:20)步骤 2:看是谁调用的 wait如果是BlockingQueue.take() / poll()→线程池空闲,正常如果是业务代码里的 wait→ 检查是否合理如果是框架内部(Looper/Handler)→ 正常步骤 3:切换 Profiler 模式Android Studio Profiler 三种模式:模式是否包含 wait/sleep何时用Sample Java Methods(采样)✅ 会显示 wait找卡顿(Wall Time)Trace Java Methods(埋点)✅ 会显示 wait精确调用链Sample C/C Functions❌ 只看 CPU找 CPU 热点System Trace(Perfetto)用线程状态色块显示最推荐想找真正的性能问题,用 System Trace,它会区分:绿色 Running(占用 CPU)蓝色 Runnable(等 CPU 调度)橙色 Sleeping(wait/sleep)红色 Uninterruptible sleep(IO 等)只有绿色和蓝色才是CPU 相关问题,橙色的 wait 是正常挂起。七、可视化对比火焰图看到的(Wall Time 视角)Worker.run ████████████████████████ 3000ms └─ wait ████████████████████████ 2980ms ← 看起来很吓人 └─ work ▌20msSystem Trace 看到的(真实占用)CPU 时间轴: Worker ─░░░░░░░░░░░░░░░░░░░░░░░█─ └─ 睡眠(2980ms) └─ 工作(20ms) └─ CPU 占用 0 └─ CPU 占用 100%结论:这个线程99.3% 的时间在睡觉,只有 20ms 真正在工作,CPU 是被别人用了。八、实战决策树看到 Object.wait / Thread.sleep 大 Self Time │ ├─ 是哪个线程? │ ├─ 主线程 → ⚠️ 严重问题,必查 │ ├─ 关键业务线程 → ⚠️ 检查是否合理 │ └─ 线程池 Worker / Handler 线程 → ✅ 通常正常 │ ├─ 调用者是谁? │ ├─ BlockingQueue.take/poll → ✅ 空闲等任务,正常 │ ├─ Looper.loop / nativePollOnce → ✅ 消息循环,正常 │ ├─ CountDownLatch.await → 检查发送端 │ ├─ 业务代码显式 wait/sleep → ⚠️ 检查逻辑 │ └─ 锁竞争(waiting on monitor) → ⚠️ 优化锁 │ └─ Profiler 是什么模式? ├─ Sample/Trace → 显示 Wall Time,wait 出现正常 └─ System Trace → 看线程状态色块更准确九、总结要点Object.wait/Thread.sleep的 Self Time 大 ≠ 性能问题本质是挂钟时间,不是CPU 时间,线程在挂起状态要看是谁在 wait:空闲的线程池 Worker → 正常主线程 → 严重问题显式业务 wait → 具体分析推荐用 System Trace(Perfetto)而不是采样 Profiler真正的 CPU 热点一般是循环、序列化、加解密、算法等,不会是 wait