ANR产生原因及分类
ANR的产生需要同时满足三个条件
1.主线程:只有应用程序进程的主线程响应超时才会产生ANR;
2.超时时间:产生ANR的上下文不同,超时时间也不同,但只要超过这个时间上限没有响应就会产生ANR;
3.输入事件/特定操作:输入事件是指按键、触屏等设备输入事件,特定操作是指BroadcastReceiver和Service的生命周期中的各个函数调用。
产生ANR的上下文不同,导致ANR原因也不同,主要有以下三种情况:
· 应用进程的主线程对输入事件在5s内没有处理完毕;
· 应用进程的主线程在执行BroadcastRecevier的onReceive函数时10s内没有处理完毕;
· 应用进程的主线程在执行Service的各个生命周期函数时20s内没有处理完毕;
AMS在发生ANR时,会按以下格式将ANR信息记录到logcat日志中:
ActivityManager: ANR in [Process Name]([short component name])
ActivityManager: PID: [Application Pid]
ActivityManager: Reason: [Annotation]
ActivityManager: Parent: [short component name of parent]
[Process CPU state]
其中的Reason信息可以给出ANR产生原因的一些详细信息,
·输入事件处理引起的ANR,提示信息格式为:“Inputdispatching timed out [anr reason]”
·Service处理引起的ANR,提示信息格式为:“Executingservice [Package name]/[Short class name]”
·Broadcast处理引起的ANR,提示信息格式为:“Broadcast of [Intent focused byBroadcast receiver]”
其中的Process CPU state 信息格式如下:
ActivityManager: Load: [Load1] / [Load5] / [Load15]
ActivityManager: CPU usage from [上次采样与现在的时间差] ms to [当前采样与现在的时间差] ms ago/later:
ActivityManager: [总的CPU时间占用率]% [PID]/[Process Name]: [用户CPU时间占用率]% user + [系统CPU时间占用率]% kernel + [IO等待CPU时间占用率]% iowait + [硬中断CPU时间占用率]% irq + [软中断CPU时间占用率]% softirq / faults: [次要页错误/主要页错误] minor/major
ActivityManager: .....
ActivityManager: [CPU时间占用率合计统计]
其中Load1, Load5, Load15分别为CPU 1分钟平均任务负载数,5分钟平均任务负载数,15分钟平均任务负载数,平均任务负载数和CPU占用率并没有必然联系,可以作为参考信息。可以通过分析各进程的CPU时间占用率,来判断是否为某些进程长期占用CPU导致该进程无法获取到足够的CPU处理时间,而导致发生ANR。
这里需要重点关注的是Load1,各进程总的CPU时间占用率,用户CPU时间占用率,系统CPU时间占用率,以及iowait CPU时间占用率。
ActivityManagerService的appNotResponding方法在写入logcat日志的同时,还会将ANR时的stack trace信息写入到trace文件。
类型一:等待binder调用返回
SYS_BINDER_INFO这个文件查找当前这个线程在和谁通信
类型二:主线程等待锁
主线程的CallStack DVM thread Status是Blocked
类型三:IO Wait问题
这种情况一般是和文件操作相关,判断是否是这种情况。IO占比很高,这个时候就需要查看trace日志看当时的callstack,着重看有没有file相关的动作。
[FAQ13684] iowait问题成因及对策
成因:
1. 系统因为io导致进程wait;
2. 数据请求量大;
3. 存储器性能欠佳(TLC的eMMC和SD Card表现明显)
原理:
Linux 用pdflush进程把数据从缓存页写入硬盘,pdflush写入硬盘看两个参数:
1. 数据在页缓存中是否超出3秒 (default),如果是,标记为脏页缓存;
/proc/sys/vm/dirty_expire_centiseconds
2. 脏页缓存是否达到工作内存的10%(default);
/proc/sys/vm/dirty_background_ratio
以下参数也会影响到pdflush
/proc/sys/vm/dirty_ratio (default 20): global_dirtyable_memory的最大百分比,系统所能拥有的最大脏页缓存的总量。
超过这个值,开启pdflush写入硬盘。如果cache增长快于pdflush,那么整个系统在20%的时候遇到I/O瓶颈,所有的I/O都要等待cache被pdflush进硬盘后才能重新开始。
对于有高度写入操作的系统
dirty_background_ratio: 主要调整参数。如果需要把缓存持续的而不是一下子大量的写入硬盘,降低这个值。
dirty_ratio:第二调整参数。
对策:
如果有大量的写操作,为避免I/O的长时间等待,可以设置:
$ echo 3 > /proc/sys/vm/dirty_background_ratio
$ echo 10 > /proc/sys/vm/dirty_ratio
可以在init.rc中修改,如果user版本(未开mtklog)难以复现,建议不做修改。
因为通过调整内核参数,将写活动的高峰分布成频繁的多次写,每次写入的数据比较少。
以这种方式执行的效率比较低,减少了内核组合写操作的机会。
类型四:主线程作耗时动作
耗时操作造成主线程堵塞
类型五:binder线程被占满
系统对每个process最多分配15个binder线程
这个是谷歌的设计(/frameworks/native/libs/binder/ProcessState.cpp)
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
#define DEFAULT_MAX_BINDER_THREADS 15 //支持最大线程数为15
如果另一个process发送太多重复binder请求,那么就会导致接收端binder线程被占满,从而处理不了其它的binder请求
这本身就是系统的一个限制,如果应用未按照系统的要求来实现对应逻辑,那么就会造成问题。
而系统端是不会(也不建议)通过修改系统行为来兼容应用逻辑,否则更容易造成其它根据系统需求正常编写的应用反而出现不可预料的问题。
判断Binder是否用完,可以在trace中搜索关键字"binder_f",如果搜索到则表示已经用完,然后就要找log其他地方看是谁一直在消耗binder或者是有死锁发生,对于binder用完的前期思路大致如此。
binder内存耗尽导致进程被kill的案例
遇到一起SystemUI Crash的案例,原因是因为SystemUI进程的binder内存空间被消耗完毕,导致ActivityManagerService 向SystemUI发送广播的时候发生了RemoteException,进而导致AMS kill掉SystemUI进程。
四大组件和AMS之间的通信都是通过binder,同时,APP层和Framework服务之间的通信大部分也是通过binder,少数的通过Socket与Pipe。 系统为每个进程分配的Binder空间为BINDER_VM_SIZE(1*1024*1024) - (4096 *2)。binder通信分为同步通信和异步通信。client等待service处理完毕之后接着执行称为同步通信;client发送请求之后,立刻返回不等待service处理完毕称之为异步通信。binder用于异步通信分配的内存空间为BINDER_VM_SIZE /2.
如果一个client给一个service端高频率的发送异步消息,而service端来不及处理,就会导致service端的binder异步通信内存空间耗尽。 这时候其他的client给service再发送异步消息的时候由于内存不够就会失败,client端会收到一个异常。AMS给我们的应用发送广播,这种情况下AMS为client, 而我们的应用暂且可以称之为service。
在我们写binder通信代码的时候,特别要注意发送数据的频率和大小,这直接会影响我们代码的健壮性。
类型六:JE或者NE导致ANR
此类问题需查看具体log看分析原因。
Linux 信号机制
信号机制是 Linux 进程间通信的一种重要方式,Linux 信号一方面用于正常的进程间通信和同步,如任务控制(SIGINT, SIGTSTP,SIGKILL, SIGCONT,……);另一方面,它还负责监控系统异常及中断。 当应用程序运行异常时, Linux 内核将产生错误信号并通知当前进程。 当前进程在接收到该错误信号后,可以有三种不同的处理方式。
1. 忽略该信号。
2. 捕捉该信号并执行对应的信号处理函数(signal handler)。
3. 执行该信号的缺省操作(如 SIGTERM, 其缺省操作是终止进程)。
当 Linux 应用程序在执行时发生严重错误,一般会导致程序 crash。其中,Linux 专门提供了一类 crash 信号,在程序接收到此类信号时,缺省操作是将 crash 的现场信息记录到 core 文件,然后终止进程。
Crash信号列表
Signal |
Description |
SIGSEGV |
Invalid memory reference. |
SIGBUS |
Access to an undefined portion of a memory object. |
SIGFPE |
Arithmetic operation error, like divide by zero. |
SIGILL |
Illegal instruction, like execute garbage or a privileged instruction |
SIGSYS |
Bad system call. |
SIGXCPU |
CPU time limit exceeded. |
SIGXFSZ |
File size limit exceeded. |
类型七:容易出现在Monkey测试下
Zram Swap
是 Linux 内核中采用时间换空间的一种技术,是把物理内存的一部分划分出来,把不是常用的内存数据压缩后放到zram里, 用到的时候把数据解压出来, 相当于牺牲了一些cpu效率,变相增大了内存。它通过压缩内存(Zram)来作为交换分区,通过压缩比来获取更多可利用的内存空间。
磁盘 swap:是把磁盘的一部分作为内存, 对应用来说完全是透明的,相当与增大了内存, 但是缺点很明显, 当用到swap的时候,速度会变的很慢。
swap分区的作用是当物理内存不足时,会将一部分硬盘当做虚拟内存来使用。
kswapd0:kswapd0进程的作用它是虚拟内存管理中,负责换页的,操作系统每过一定时间就会唤醒kswapd ,看看内存是否紧张,如果不紧张,则睡眠。
kswapd0 占用过高是因为 物理内存不足,使用swap分区与内存换页操作交换数据,导致CPU占用过高。
mmcqd:是与sd卡读取文件有关的进程
死锁