Android ANR的trace文件基本信息解读

本文详细介绍了如何获取Android ANR的trace文件,以及如何解析线程基本信息、线程状态和堆栈信息。通过对Blocked、Native、Waiting、Sleeping等状态的分析,揭示了导致ANR的各种原因,如锁等待、JNI调用、线程挂起、内存管理问题等,并给出了相应的优化建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ANR堆栈文件的抓取

  • android6以下可以直接读取/data/anr目录下的trace.txt文件,然后过滤出自己进程的anr文件即可
  • android6及以上,没有读取/data/anr的权限,需要native层通过拦截SIGQUIT信号,结合消息机制msg的when超时时间,底层dump堆栈信息。详细原理详见微信技术团队的分享:微信Android客户端的ANR监控方案

Android ANR的trace文件基本信息解读

  • 示例
"main" prio=5 tid=1 Native
  | group="main" sCount=0 dsCount=0 flags=0 obj=0x727c02f8 self=0xb400007a2f210800
  | sysTid=339 nice=-10 cgrp=default sched=0/0 handle=0x7ab698d500
  | state=? schedstat=( 0 0 0 ) utm=0 stm=0 core=0 HZ=100
  | stack=0x7fc8197000-0x7fc8199000 stackSize=8192KB
  | held mutexes=
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:339)
  at android.os.Looper.loop(Looper.java:200)
  at android.app.ActivityThread.main(ActivityThread.java:8312)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)
线程基本信息
  • 线程优先级:prio=5 (主线程均是5)
  • 线程ID: tid=1 主线程的id一般都是1
  • 线程状态:Native :表示正在调用JNI
    • 还有其他多种状态,表示发生ANR时主线程的状态
  • 线程组名称:group=“main”
  • 线程被挂起的次数:sCount=0
    • 在等待GC时,有时候挂起的线程数量比较多
  • 线程被调试器挂起的次数:dsCount=0
  • 线程的java的对象地址:obj= 0x7682ab30
  • 线程本身的Native对象地址:self=0x7bd3815c00线程调度信息:
线程优先级信息
  • Linux系统中内核线程ID: sysTid=6317与主线程的进程号相同
  • 线程调度优先级:nice=-10
  • 线程调度组:cgrp=default
  • 线程调度策略和优先级:sched=0/0
  • 线程处理函数地址:handle= 0x7c59fc8548
线程状态信息
  • 线程调度状态:state=S
  • 线程在CPU中的执行时间、线程等待时间、线程执行的时间片长度:schedstat=(1009468742 32888019 224)
  • 线程在用户态中的调度时间值:utm=91
  • 线程在内核态中的调度时间值:stm=9
  • 最后执行这个线程的CPU核序号:core=4
线程堆栈信息
  • 堆栈地址和大小:stack=0x7ff27e1000-0x7ff27e3000 stackSize=8MB

ANR中的主线程状态及ANR示例分析

Blocked
  • case 1
"main" prio=5 tid=1 Blocked
  | group="main" sCount=0 dsCount=0 flags=0 obj=0x72996758 self=0xb400007ac7e10800
  | sysTid=28366 nice=-10 cgrp=default sched=0/0 handle=0x7b4f71b500
  | state=? schedstat=( 0 0 0 ) utm=0 stm=0 core=0 HZ=100
  | stack=0x7fe0a67000-0x7fe0a69000 stackSize=8192KB
  | held mutexes=
  at com.autonavi.base.ae.gmap.GLMapEngine.addGestureMessage(SourceFile:-1)
  - waiting to lock <0x0f62c521> (a com.autonavi.base.ae.gmap.GLMapEngine) held by thread 96
  at com.amap.api.mapcore.util.c.addGestureMapMessage(SourceFile:2892)
  at com.amap.api.mapcore.util.p$c.b(SourceFile:801)
  at com.amap.api.mapcore.util.am.a(SourceFile:104)
  at com.amap.api.mapcore.util.ak.d(SourceFile:61)
  at com.amap.api.mapcore.util.p.a(SourceFile:195)
  at com.amap.api.mapcore.util.c.onTouchEvent(SourceFile:1795)
  at com.amap.api.mapcore.util.e.onTouchEvent(SourceFile:47)
  • 分析:
    • GLMapEngine锁被线程96持有,主线程一直等待到ANR,需要解决锁等待的问题
  • case 2
"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 cgrp=apps/bg_non_interactive handle=0xb6f4fb4c
  | sysTid=4232 nice=0 sched=0/0 cgrp=apps/bg_non_interactive handle=0xb6f4fb4c
  | state=S schedstat=( 93912199183 67364592009 177636 ) utm=7567 stm=1824 core=1 HZ=100
  | heldMutexes=
 at android.hardware.display.DisplayManagerGlobal.getDisplayInfo(DisplayManagerGlobal.java:172)
 at android.hardware.display.DisplayManagerGlobal.getDisplayInfo(DisplayManagerGlobal.java:166)
 at android.hardware.display.DisplayManagerGlobal.getDisplayInfo(DisplayManagerGlobal.java:159)
  • 分析
    • cgrp=apps/bg_non_interactive 表示app在后台;
    • sCount=1有一个线程被挂起;
    • nice=0 压后台时,线程优先级降低,nice值越小(负数),优先级越高
    • schedstat( 93912199183 67364592009 177636 ),表示线程执行时间很长
Native
  • case 1
"main" prio=5 tid=1 Native
  | group="main" sCount=0 dsCount=0 flags=0 obj=0x727c02f8 self=0xb400007a2f210800
  | sysTid=339 nice=-10 cgrp=default sched=0/0 handle=0x7ab698d500
  | state=? schedstat=( 0 0 0 ) utm=0 stm=0 core=0 HZ=100
  | stack=0x7fc8197000-0x7fc819900
<think>我们被要求解释Android线程堆栈信息中的各个字段的含义。根据用户提供的堆栈示例,我们将逐一解释每个字段。 注意:用户提供的示例堆栈如下: "io_thread" prio=5 tid=3 Native | group="main" sCount=2 ucsCount=0 flags=1 obj=0x1343f8a8 self=0xb400007a6b97c720 | sysTid=17024 nice=0 cgrp=foreground sched=0/0 handle=0x78d35fccb0 | state=S schedstat=( 5906823 3810416 45 ) utm=0 stm=0 core=2 HZ=100 | stack=0x78d34f9000-0x78d34fb000 stackSize=1039KB | held mutexes= 我们将按照行进行分解: 1. 第一行:线程名称和基本属性 - "io_thread": 线程的名称,通常由开发者指定,如果没有指定则使用默认名称。 - prio=5: 线程的优先级。在Android中,优先级范围通常为1-10,其中5是普通优先级。 - tid=3: 线程的ID(thread identifier),这是一个Java层的线程ID,由虚拟机分配。 - Native: 表示线程当前正在执行本地(native)代码,即JNI方法。 2. 第二行:线程分组和状态计数 - group="main": 线程所属的线程组。主线程通常属于"main"组,其他线程可能属于其他组。 - sCount=2: 挂起计数(suspend count),表示线程被挂起的次数。0表示正常运行,大于0表示被挂起。 - ucsCount=0: 未强制挂起计数(unforced suspend count)。在调试时,线程可能被调试器挂起,但这里是指非调试器导致的挂起计数。 - flags=1: 线程标志位,通常是一个位掩码,表示线程的状态标志。例如,1可能代表线程处于活动状态(TERMINATED标志为0)。 - obj=0x1343f8a8: 该线程在虚拟机中的对象地址。 - self=0xb400007a6b97c720: 线程本身的本地线程结构(pthread)的地址。 3. 第三行:系统线程ID和调度信息 - sysTid=17024: 系统线程ID(即Linux内核中的线程ID),可以使用此ID在系统级工具(如top)中跟踪线程。 - nice=0: 线程的nice值,用于调整线程的调度优先级。nice值范围从-20(最高优先级)到19(最低优先级),0为默认值。 - cgrp=foreground: 线程所属的cgroup(控制组)。"foreground"表示该线程属于前台进程组,通常意味着它可以获得更多的CPU资源。 - sched=0/0: 调度策略和优先级。第一个数字表示调度策略(0表示SCHED_NORMAL,即CFS调度),第二个数字是实时优先级(非实时调度为0)。 - handle=0x78d35fccb0: 线程句柄,用于内部引用。 4. 第四行:线程状态和CPU时间 - state=S: 线程的当前状态。状态代码是Linux标准的状态: - R: 运行中(Running)或可运行(Runnable) - S: 休眠(Sleeping),通常是在等待某个事件(如I/O) - D: 不可中断的休眠(通常是在等待I/O) - Z: 僵尸进程 - T: 被跟踪或停止(例如,被调试器暂停) - schedstat=( 5906823 3810416 45 ): 调度统计信息,三个数字分别表示: - 在就绪队列上等待的时间(单位:纳秒) - 实际执行的时间(单位:纳秒) - 时间片(time slice)剩余时间(单位:毫秒)?实际上,第三个值在较新版本中可能表示运行的时间片数量,但具体含义可能因内核版本而异。通常,我们关注前两个值。 - utm=0: 用户态下消耗的CPU时间(单位:jiffies,一个jiffy通常是10毫秒?注意,这里HZ=100,所以1 jiffy=10ms?但utm和stm的单位是百分之一秒?实际上,在Linux中,jiffies的单位取决于HZ。这里HZ=100,所以1 jiffy=10ms。但utm和stm的值通常是以jiffies为单位,所以需要乘以10得到毫秒。但注意,在Android的线程堆栈中,utm和stm有时直接是毫秒?需要根据上下文判断。不过,在提供的示例中,utm=0, stm=0,说明这个线程几乎没有占用CPU时间。 - stm=0: 内核态下消耗的CPU时间(单位同上)。 - core=2: 线程最后运行所在的CPU核心编号。 - HZ=100: 系统时钟中断频率,这里是100Hz,即每秒钟100次中断。 5. 第五行:栈信息 - stack=0x78d34f9000-0x78d34fb000: 线程栈的起始地址和结束地址(内存地址范围)。 - stackSize=1039KB: 栈的大小(以KB为单位)。 6. 第六行:持有的互斥锁 - held mutexes=: 线程当前持有的互斥锁列表。在这个例子中为空,表示没有持有任何互斥锁。 另外,在堆栈跟踪部分,我们还可以看到: native: #00 pc 000afe4c /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+12) 这表示在本地代码中的调用栈,pc是程序计数器(当前指令地址),后面是所在的库和函数名。 总结: 线程堆栈信息提供了线程的详细状态,包括优先级、ID、状态、CPU使用情况、栈信息等,对于分析线程行为和性能问题非常有用。 注意:用户提供的引用[1]中提到了在debug模式下Thread Dump中看到nativePollOnce的异常堆栈,但用户的应用运行正常,且这些线程都处于runnable状态。实际上,nativePollOnce是Android消息循环中的正常调用,它用于等待消息。在runnable状态表示线程正在运行或等待I/O(实际上在Linux中,runnable状态是R,而S是休眠)。所以,如果线程状态是S,而nativePollOnce出现在堆栈中,这是消息队列等待消息的正常现象。 因此,在分析线程堆栈时,状态字段(state)非常重要: - 如果状态是R(或Running),说明线程正在使用CPU。 - 如果状态是S,说明线程在等待某个事件(如I/O完成、消息队列等),这通常是正常的阻塞。 以上是对Android线程堆栈中各个字段的解释。</think>### Android 线程堆栈字段详解 以下是 Android 线程堆栈中各字段的详细解释(基于您提供的堆栈示例): #### 1. **基础标识字段** ```plaintext "io_thread" prio=5 tid=3 Native ``` - **"io_thread"**:线程名称,开发者自定义或系统自动生成 - **prio**:线程优先级(1-10),值越大优先级越高 - $5$ 为默认优先级,主线程通常为 $5$ - **tid**:Java 层线程 ID(虚拟机分配) - **Native**:线程当前状态(常见值) - `Native`:执行 JNI 本地代码 - `Runnable`:可运行状态 - `Waiting`:等待唤醒 #### 2. **虚拟机元数据** ```plaintext | group="main" sCount=2 ucsCount=0 flags=1 obj=0x1343f8a8 self=0xb400007a6b97c720 ``` - **group**:线程组名称(主线程组为 `"main"`) - **sCount**:挂起计数器(Suspend Count) - $0$:正常运行 - $>0$:被调试器挂起 - **ucsCount**:未强制挂起计数(Unforced Suspend Count) - 调试时非强制挂起的次数 - **flags**:线程状态标志位(位掩码) - $flags=1$:`THREAD_STARTED`(已启动) - **obj**:线程对象在 JVM 中的内存地址 - **self**:本地线程结构(`pthread_t`)地址 #### 3. **系统级信息** ```plaintext | sysTid=17024 nice=0 cgrp=foreground sched=0/0 handle=0x78d35fccb0 ``` - **sysTid**:操作系统线程 ID(Linux 内核 PID) - **nice**:调度优先级调整值($[-20,19]$) - $0$:默认优先级 - 负值:更高优先级(如实时线程) - **cgrp**:控制组(CGroup) - `foreground`:前台进程组(CPU 资源优先) - `background`:后台进程组(资源受限) - **sched**:调度策略/优先级 - `0/0`:`SCHED_NORMAL`(默认策略) - `1/99`:`SCHED_FIFO`(实时调度) - **handle**:内部线程句柄地址 #### 4. **运行时状态** ```plaintext | state=S schedstat=( 5906823 3810416 45 ) utm=0 stm=0 core=2 HZ=100 ``` - **state**:内核级线程状态(关键字段) - `S`:休眠(Sleeping),等待 I/O 或事件 - `R`:运行中(Running/Runnable) - `D`:不可中断休眠(通常为 I/O 阻塞) - `Z`:僵尸进程 - **schedstat**:调度统计(单位:纳秒) - $(T_1, T_2, T_3)$: - $T_1$:就绪队列等待时间 - $T_2$:CPU 执行时间 - $T_3$:时间片剩余量 - **utm**:用户态 CPU 时间(单位:jiffies) - $1 \text{jiffy} = \frac{1}{HZ} \text{秒}$ - **stm**:内核态 CPU 时间 - **core**:最后运行的 CPU 核心编号 - **HZ**:系统时钟频率(100Hz = 10ms/周期) #### 5. **内存信息** ```plaintext | stack=0x78d34f9000-0x78d34fb000 stackSize=1039KB ``` - **stack**:线程栈内存地址范围 - **stackSize**:栈大小(影响递归深度) #### 6. **同步状态** ```plaintext | held mutexes= ``` - **held mutexes**:线程持有的锁 - 此处为空表示未持有任何锁 - 若显示 `Mutex@0xaddr` 表示持有一个锁 #### 7. **本地堆栈跟踪** ```plaintext native: #00 pc 000afe4c /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+12) ``` - **native: #nn**:本地调用栈层级 - **pc**:程序计数器地址 - **库路径+函数名**:执行的本地函数 - 示例中为 `libc.so` 的 `epoll_pwait` 系统调用 ### 关键状态解读示例 在您提供的堆栈中: ```plaintext state=S schedstat=(5906823 3810416 45) utm=0 stm=0 ``` - **状态分析**: - `state=S`:线程在等待 I/O 事件(如网络/文件读写) - `utm=0, stm=0`:未消耗 CPU 时间,处于空闲等待 - `schedstat`:$5.9ms$ 等待调度,$3.8ms$ 执行时间 - 符合 `epoll_pwait` 的阻塞特性[^1] ### 诊断建议 当线程长期处于 `state=S` 时: 1. 检查 I/O 资源(网络延迟/磁盘速度) 2. 分析 `schedstat` 中等待时间占比 $W = \frac{T_1}{T_1+T_2}$ - 若 $W > 0.8$ 表明存在资源瓶颈 3. 确认 `held mutexes` 是否为空(避免死锁) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值