在 Android 上排查进程的线程阻塞问题,可以通过以下步骤结合 ADB 工具和系统级命令进行诊断。以下是一个系统化的排查指南:
一、快速确认线程状态
1. 查看进程的所有线程及 CPU 占用
通过 top
或 ps
命令观察线程的 CPU 和状态:
# 查看进程 489 的所有线程及 CPU 使用率
adb shell "top -H -p 489"
# 或过滤线程信息
adb shell "ps -T -p 489"
- 关键字段:
PID
(进程ID)和TID
(线程ID)S
(状态):R
=运行中,S
=睡眠,D
=不可中断的睡眠(通常为 I/O 阻塞),Z
=僵尸进程%CPU
:长时间高占用可能是计算阻塞,0%
可能为等待锁或 I/O
2. 获取线程的 Java/Native 堆栈
生成线程堆栈快照,检查阻塞位置:
# 触发线程堆栈转储(需要进程有响应)
adb shell kill -3 489
# 从设备拉取堆栈文件(路径因系统而异,通常为 /data/anr/traces.txt 或 /data/tombstones/)
adb pull /data/anr/traces.txt
- 分析重点:
- 查找状态为
BLOCKED
或WAITING
的线程。 - 检查是否有锁竞争(如
waiting on <0x12345678>
或locked <0x12345678>
)。
- 查找状态为
二、深入诊断阻塞原因
1. 检查线程的内核态堆栈
查看线程在内核中的阻塞点(需 root):
# 列出进程的所有线程
adb shell "ls /proc/489/task/"
# 查看某个线程的内核堆栈(替换 [TID] 为实际线程ID)
adb shell "cat /proc/489/task/[TID]/stack"
- 常见阻塞场景:
futex_wait_queue_me
:用户态锁竞争。epoll_wait
:I/O 多路复用等待。io_schedule
:等待 I/O 操作完成。
2. 使用 strace
跟踪系统调用(需 root)
监控线程的系统调用,定位阻塞的源头:
# 附加到目标线程(替换 [TID] 为实际线程ID)
adb shell "strace -p [TID] -f -tt -T"
- 关键观察点:
- 长时间卡在
read
/write
:可能为 I/O 阻塞。 - 频繁
futex
调用:锁竞争或条件变量等待。 poll
/select
/epoll
:网络或文件描述符等待。
- 长时间卡在
3. 检查文件描述符和网络连接
确认线程是否因 I/O 或网络操作阻塞:
# 查看进程打开的文件描述符
adb shell "ls -l /proc/489/fd/"
# 检查网络连接状态
adb shell "netstat -anp | grep 489"
三、常见阻塞场景及解决方向
1. 锁竞争(Lock Contention)
- 现象:线程状态为
BLOCKED
,堆栈显示waiting on <lock>
。 - 解决:检查代码中的同步块(
synchronized
)、ReentrantLock
或pthread_mutex
。
2. I/O 阻塞
- 现象:线程状态为
RUNNABLE
但 CPU 占用低,内核堆栈显示io_schedule
。 - 解决:检查文件读写、网络请求(如未使用非阻塞 I/O)。
3. 死锁(Deadlock)
- 现象:多个线程互相等待对方持有的锁。
- 解决:从堆栈中找到循环等待的锁链,优化锁顺序。
4. 主线程阻塞(ANR)
- 现象:主线程执行耗时操作(如数据库、网络请求)。
- 解决:将耗时任务移至子线程,使用
AsyncTask
或HandlerThread
。
四、自动化工具辅助
1. Android Profiler
- 在 Android Studio 中使用 CPU Profiler,录制方法调用轨迹,定位耗时操作。
- 使用 Network Profiler 检查网络请求阻塞。
2. Systrace
生成系统级跟踪报告:
adb shell "atrace -b 32768 -t 10 sched gfx view wm am" > trace.html
- 分析线程调度延迟和锁信息。
五、总结步骤
- 快速定位可疑线程:通过
top
/ps
查看线程状态和 CPU。 - 捕获堆栈:使用
kill -3
或traces.txt
分析阻塞点。 - 内核级诊断:查看
/proc/[tid]/stack
和strace
。 - 结合代码和日志:根据堆栈信息回溯代码逻辑。
通过以上方法,你可以逐步缩小范围,确定线程阻塞的具体原因。如果需要更具体的分析,可以提供线程堆栈片段或代码上下文!