稳定性优化:ANR 分析思路
这里笔者简单的模拟一个 ANR,以此来带着读者掌握 ANR 分析的思路。如图所示,主线程会因为等锁而产生 ANR。
ANR 的分析和排查往往要比 Crash 要复杂很多,所以当 ANR 发生后,最重要的一件事就是要获取足够的日志信息进行分析,最关键的日志包括 data/anr 目录下的日志信息以及 Log 日志信息,我们可以用前面介绍的方案去抓取这些日志,或者可以通过 BugReport等线下方式拿到这些日志信息。当有了这些日志信息后,就可以开始进行 ANR 的分析了。
1 初步分析
在分析 ANR 时,我们首先要根据日志进行初步的分析,来确定 ANR 发生的时间、类型、大致的原因。通过运行上面的示例逻辑后,便会发生 ANR 并在 /data/anr 目录中生成 ANR 日志,部分 ANR 日志信息如下图所示。
如日志所示,在第 1 行可以确定该 ANR 类型是 InputDispatching TimedOut,即输入事件分发超时。接着随后几行可以知道 ANR 发生的时间,线程 id 等信息。在第 10 行开始可以进一步知道主线程的优先级、锁、运行状态、堆栈等信息,其中最关键的就是主线程的运行状态,常见有以下几种状态
- Runnable:线程可运行或者正在运行
- Sleeping:代码逻辑中调用了 wait、sleep、或 join 等函数让线程进入休眠
- Blocked:线程阻塞,此时一般都是在等待获取锁对象
- Waiting:代码中执行了没有设置超时参数的 wait 函数导致线程进入等待状态
可以看到日志中主线程的状态是 Blocked,此时我们也基本能确定这是一个等待锁导致的 ANR。
2 性能分析
因为示例中的 ANR 是一个比较简单的 ANR,通过基本的分析,就能知道发生 ANR 原因是因为主线程等锁导致的,但实际中很多 ANR 都没那么简单,特别是主线程的状态处于 Runnable 状态时,此时主线程处于运行状态却发生了 ANR,这种情况一般都是没法通过简单的初步分析就能定位到原因,此时我们接着需要进行性能分析,来确认是否是性能导致的 ANR 异常。
详细的性能相关的数据没有在 Trace 日志中,而是在 Log 日志中,根据 Logcat 中的日志信息,如下图所示,可以看到发生 ANR 时的性能状况。
在对性能进行分析时,我们主要分析的方向主要下面这些:
- 看 CPU LOAD Average(负载平均值)数据
在性能日志中可以看到 “Load: 0.74 / 0.56 / 0.55” 这一行,它表示过去 1、5、15 分钟内正在使用或者等待使用 CPU 的进程数的平均值,这个值也称为负载平均值,是一个用来衡量系统负载的指标。负载平均值一般不应该超过处理器核心的总数量,如果超过了则说明系统的负载较大,CPU 的资源不足。
- 看进程 CPU 占用率
通过日志中 “CPU usage from 254840ms to 0ms ago” 这一行信息,我们可以知道在过去的时间段内(从过去 254840 ms 到 0 ms 之间)CPU 的使用情况。日志中会按照 CPU 使用率从高到低将进程信息打印出来,包括 CPU 使用率、进程号、进程名、应用进程(user)和系统进程(kernel) CPU 占比等详细的数据。
- 看其他关键进程或指标
查看 kswapd0,iowait 等关键字来判断是否由于系统的内存或 IO 的问题导致的 ANR 。kswapd0 是管理内存的进程,当内存空间不足时,kswapd0 进程会负责将不常使用的页面换出到交换空间,如果该进程占用率很高时,说明设备处于低内存状态且正在频繁的发生换页操作。
iowait 是系统状态的一个指标,表示CPU在等待 IO 操作完成的时间比例。高 iowait 值通常表示系统中的 IO 操作存在异常,可能导致CPU资源闲置,因而产生 ANR。
- 内存等其他性能数据
最后可以查看一下内存使用情况,包括分析内存信息,如总分配字节、释放字节、可用内存等,以判断是否存在内存不足或频繁的内存交换问题,并结合线程数量,磁盘占用,机型等综合因素来进一步判断性能是否存在异常。
通过这些性能数据的分析,我们基本就可以确认是否是性能导致的 ANR,如果确认是性能问题导致的 ANR,就需要转换成对内存,CPU 等性能问题的治理,如果确认不是性能导致的 ANR,就需要进一步来分析导致 ANR 的原因。
3 直接和间接分析
线程的状态如果处于 Block、Waiting 等状态,这通常意味着主线程被阻塞或正在等待某些操作完成,由于有阻塞任务的存在,所以主线程在规定时间内无法响应四大组件的任务而导致了 ANR 的发生,如下图所示。此时我们可以直接的通过分析主线程的堆栈,看主线程当前的任务中是否有等锁,死锁等情况,或者是否存在数据库读写,大文件 IO 等耗时的操作,就能分析出 ANR 的原因。通过 ANR 日志第 10 行可以看到主线程处于 Block 状态,进一步看日志堆栈,可以看到 "waiting to lock held by thread 4" 这行日志,说明主线程在等待线程号为 4 的线程所持有的锁,接着通过堆栈就能明确的定位到等锁的地方,位于 StabilityExampleActivity 的 34 行,这就是导致 ANR 产生的根源。
如果主线程的状态处于 Running 状态,那么就说明并不是当前堆栈导致的 ANR,而是当前堆栈之前的某些函数导致,这种情况下可能是主线程中的任务过多,也可能是主线程中的某一些任务的耗时较久,这些累积起来导致主线程没法及时响应四大组件的任务,如下图所示。
这种 ANR 没法通过直接的堆栈分析来排查,所以一般排查起来也是比较复杂的,此时通常都会间接转换成对耗时函数的分析,常见的耗时函数主要是逻辑复杂、IO 慢,等待锁等因素导致,我们需要通过线下的 Trace 日志或者线上的耗时方法的采集,减少函数耗时,以此来间接减少这类 ANR 发生的概率。
转载自:https://juejin.cn/post/7372911159350050867