"Signal Catcher" daemon prio=5 tid=6 Runnable
| group="system" sCount=0 dsCount=0 flags=0 obj=0x15b802d8 self=0x756d5cc2e000
| sysTid=7781 nice=0 cgrp=default sched=0/0 handle=0x756d5c9ffd50
| state=R schedstat=( 9996973 11775302 9 ) utm=0 stm=0 core=3 HZ=100
| stack=0x756d5c909000-0x756d5c90b000 stackSize=991KB
| held mutexes= "mutator lock"(shared held)
native: #00 pc 000000000048df0e /apex/com.android.runtime/lib64/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, int, BacktraceMap*, char const*, art::ArtMethod*, void*, bool)+126)
native: #01 pc 00000000005a77b3 /apex/com.android.runtime/lib64/libart.so (art::Thread::DumpStack(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, bool, BacktraceMap*, bool) const+675)
native: #02 pc 00000000005c49bb /apex/com.android.runtime/lib64/libart.so (art::DumpCheckpoint::Run(art::Thread*)+859)
native: #03 pc 00000000005bcf18 /apex/com.android.runtime/lib64/libart.so (art::ThreadList::RunCheckpoint(art::Closure*, art::Closure*)+456)
native: #04 pc 00000000005bc2d1 /apex/com.android.runtime/lib64/libart.so (art::ThreadList::Dump(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, bool)+1601)
native: #05 pc 00000000005bbb88 /apex/com.android.runtime/lib64/libart.so (art::ThreadList::DumpForSigQuit(std::__1::basic_ostream<char, std::__1::char_traits<char>>&)+840)
native: #06 pc 00000000005621f9 /apex/com.android.runtime/lib64/libart.so (art::Runtime::DumpForSigQuit(std::__1::basic_ostream<char, std::__1::char_traits<char>>&)+201)
native: #07 pc 00000000005763a2 /apex/com.android.runtime/lib64/libart.so (art::SignalCatcher::HandleSigQuit()+1618)
native: #08 pc 00000000005752b4 /apex/com.android.runtime/lib64/libart.so (art::SignalCatcher::Run(void*)+244)
native: #09 pc 0000000000100fce /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+30)
native: #10 pc 0000000000098fe7 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+55)
(no managed stack frames)
第1行:“Signal Catcher”:线程名称,daemon:是否是daemon线程(如果不是,则不打印“daemon”),prio=5:java线程Thread对象中的优先级,tid=3:vm中对应的 threadid,Runnable:线程在虚拟机中的状态;(如果当前线程没有attach,则第一行显示: “name” prio=num (not attached));
第2行:group: ThreadGroup,sCount: Suspend count, dsCount: debugger suspend count(小于等于sCount),obj:对应java线程 java.lang.Thread对象,self:native 对应的 thread 指针;
第3行:sysTid:对应linux线程 tid, nice:线程调度执行优先级,-20 ~ 20 之间,越小,优先级越高, -1代表获取优先级失败,cgrp:cgroup,cpu调度group,sched:调度策略和调度优先级,handle:当前线程对应的pthread_t
第4行:state:linux线程的状态,schedstat:线程调度情况,utm=15:线程在用户态运行的时间, stm=6:线程在内核态运行的时间, core=4:线程最后运行在哪个cpu上, HZ=100:系统时钟频率。
utm,stm 单位是jiffies,时钟中断次数;频率是周期的倒数,一般是一秒钟中断产生的次数,所以 1/100 = 0.01s = 10ms, 每10ms产生一次中断;
第5行:stack=0x7fa1f14000-0x7fa1f16000 stackSize=1005KB。线程栈的start 和 end,以及 stack size;
第6行:held mutexes= “mutator lock”(shared held)
在上面我们对各类日志的关键信息进行了基本释义,下面就来介绍一下,当我们日常遇到 ANR 问题时,是如何分析的,总结思路如下:
以上分析思路,进一步细分的话,可以分为以下几个步骤:
刚才我们介绍到,上面这些关键字是反应系统 CPU,Mem,IO 负载的关键信息,在分析完主线程堆栈信息之后,还需要进一步在 ANRInfo,logcat 或 Kernel 日志中搜索这些关键字,并根据这些关键字当前数值,判断当前系统是否存在资源(CPU,Mem,IO)紧张的情况;
通过观察系统负载,则可以进一步明确是 CPU 资源紧张,还是 IO 资源紧张;如果系统负载过高,一定是有某个进程或多个进程引起的。反之系统负载过高又会影响到所有进程调度性能。通过观察 User,Sys 的 CPU 占比,可以进一步发分析当前负载过高是发生在应用空间,还是系统空间,如大量调用逻辑(如文件读写,内存紧张导致系统不断回收内存等等),知道这些之后,排查方向又会进一步缩小范围。
从上面分析,在我们知道当前系统负载过高,是发生在用户空间还是内核空间之后,那么我们就要通过 Anrinfo 的提供的进程 CPU 列表,进一步锁定是哪个(些)进程导致的,这时则要进一步的观察每个进程的 CPU 占比,以及进程内部 user,sys 占比。
在通过系统负载(user,sys,iowait)锁定方向之后,又通过进程列表锁定目标进程,那么接下来我们就可以从目标进程内部分析各个线程的(utm,stm),进一步分析是哪个线程有问题了。
在 Trace 日志的线程信息里可以清晰的看到每个线程的 utm,stm 耗时。至此我们就完成了从系统到进程,再到进程内部线程方向的负载分析和排查。当然,有时候可能导致系统高负载的不是当前进程,而是其他进程导致,这时同样会影响其他进程,进而导致 ANR。
更多信息:
除了上面的一些信息,我们还可以结合 logcat 日志分析 ANR 之前的一些信息,查看是否存在业务侧或系统侧的异常输出,如搜索“Slow operation”,“Slow delivery"等关键字。也可以观察当前进程和系统进程是否存在频繁 GC 等等,以帮忙我们更全面的分析系统状态。