Android内存异常机制(用户空间)_JE_je和ne-程序员宅基地

技术标签: jvm  python  java  android  linux  

常见的Android稳定性异常,有内核异常和Android层异常。内核异常也就是常说的“kernel panic”,简称KE异常;Android层异常又分为java层crash和Native层crash,简称JE、NE异常。此外,Android层异常还有应用ANR和system_Server watchdog异常,这两种异常是应用或者系统长时间无响应时触发的。

本文主要介绍android层Java Exception异常的抓取机制和处理方式。下期文章里会再介绍NE异常。

Java Exception

1.简介  

我们知道java有try-catch的异常捕获机制,一份健壮的代码应该在设计和实现时考虑到各种可能遇到的exception异常,捕获并做相应的处理。

但并非所有的异常都是可被预知的,没有捕获到的异常,会被一层层的往上抛,最终由虚拟机的默认异常处理机制来处理。这里会打印出异常的堆栈信息,同时程序将停止运行,也就是我们常见的程序崩溃。

Debug版本上,程序崩溃会弹框提示“xx程序停止运行”。正式用户版本上,各厂商通常都修改为直接退出应用,移除了弹框提示。

2.异常抓取机制

Java的Thread类提供了一份Java的Thread类中有一个UncaughtExceptionHandler接口,该接口的作用主要是为了当Thread因未捕获的异常而突然终止时,调用处理程序处理异常。

//UncaughtExceptionHandler接口唯一的回调函数

void uncaughtException(Thread t, Throwable e);


//设置当前线程的异常处理器
Thread.setUncaughtExceptionHandler
 

//设置所有线程的默认异常处理器
Thread.setDefaultUncaughtExceptionHandler
 

//设置所有线程的默认异常预处理器
Thread.setUncaughtExceptionPreHandler

Android中也同样利用了这种机制。虚拟机在遇到线程没有捕获的异常时,会调用thread的dispatchUncaughtException分发到当前线程中。

这里的java_lang_Thread_dispatchUncaughtException就是反射的java thread类的dispatchUnCaughtException方法。

这里面会有两个uncaughtExceptionHandler参与处理,分别是preHandler和Handler,分别执行各自的unCaughtException()方法。

这里,preHandler可以不设置。事实上,Android平台在N之前只有一个Handler,并没有设置preHandler。

我们知道,Android系统中,System_server进程和各种应用进程都是从zygote进程孵化而来,zygoteInit的时候会调用Runntime.commonInit(),这里就会设置进程默认的uncaughtExceptionHandler,代码实现如下:

这里,RunntimeInit初始化了两个handler,一个是preHandler(LoggingHandler),一个是defaultHangler(KillApplicationHandler)。

两个类都继承于Thread.UncaughtExceptionHandler,实现其中的uncaughtException方法。其中:

(1)LoggingHandler:

主要用来打印异常信息,包括是否是SYSTEM_SERVER进程异常,异常进程名、pid信息、异常堆栈等。

我们分析问题常见的“FATAL EXCEPTION:”的打印就是来自于这里。如果是SYSTEM_SERVER进程发生的JE异常,打印的头部会变成”*** FATAL EXCEPTION IN SYSTEM PROCESS:”.

(2)KillApplicationHandler:

主要用来检查和确保LoggingHandler的日志打印被调用到,然后通知AMS,杀掉应用进程。

Android N之前,只有一个defaultHandler,日志打印和通知AMS杀进程都是在这个Handler中完成的。

需要注意的是,preHandler的设置并非对外公开api,应用无法使用。但是Thread的setDefaultUncaughtExceptionHandler是公开的api,应用进程可以通过重设它来实现自己捕获uncaughtException。这种情况下,系统日志中虽然打印了FATAL EXCEPTION,但是应用并没有死掉。应用可以通过这种方式自己捕获异常的堆栈,但是通常情况下,捕捉完信息后,建议还是杀掉或者重启进程,这种如果处理不好的话,容易出现应用假死(无法运行也不会自动退出) 的情况。

在log中打印完日志之后,uncaughtException还会调用AMS的handleApplicationCrash对本次异常进行处理,将抛出的异常throwable通过ApplicationErrorReport类转换为序列化变量再通过binder传递到AMS服务端。

HandleApplicationCrash会先去获取进程名称,进程名(processName)获取方式:

①当远程Ibinder对象为空,则进程名为“system_server”

②远程对象不为空时。如果processRecord能查到符合该binder对象的app记录,则打印processRecord对象中相应的进程名;如果processRecord为空,现场可能已经不完整,进程名复制为“unknown”。

有了crashInfo,又拿到了processRecord信息和进程名,接下来继续执行crashInner处理方法。HandleApplicationCrashInner主要完成两件事情,一是调用addErrorToDropBox,将crashInfo的关键信息输出到dropbox文件中,目录位于data/system/dropbox,根据异常的进程名,一般名为system_server_crash@***.txt,system_app_crash***,data_app_crash***。

写入的内容包括进程名、pid、uid、时间、flag、包名、前后台信息、fingerprint版本信息、crashInfo堆栈信息等,厂商定制可能会再加入一些其他的打印。

到这里,日志已经保存完,接下来AppError.crashApplication用来完成最后的收尾工作,主要用来处理应用退出后带来的状态切换变化,以及呈现crash弹窗给用户。该函数中主要的两段:

① makeAppCrashingLocked。处理应用退出后的逻辑,主要完成的工作:

  • 处理屏幕旋转以及旋转动作相关逻辑;

  • 移除屏幕冻结的超时消息;

  • 使能输入事件分发;

  • 发送configuration改变的消息;

  • 恢复顶部的Activity;

② SHOW_ERROR_UI_MSG。主要用来处理crash之后的弹框提醒,阻塞并等待用户选择是否退出,用户不做选择超过5min或者手机休眠的话,会自动退出。

至此,JAVA Exception的抓取处理逻辑完成。

3.抓到的日志

JE异常产生后,会先在logcat buffer中产生一系列打印,包含刚才提到的“FATAL EXCEPTION”和“***FATAL EXCEPTION IN SYSTEM PROCESS”, 通常可以以FATAL关键字搜索定位。

LOG中的JE异常格式如下:

另外,在data/system/dropbox目录下,会同步生成一份txt的日志文件,需要root权限才能导出。通常根据crash的进程的不同,前缀可能是“system_app_crash”、“data_app_crash”、“system_server_crash”中的一种。

导出后的内容如下图:

4.异常堆栈由来

Java exception的产生,主要有两种原因:

 (1) 编写的程序代码内部错误产生的异常,如调用对象为空(空指针异常)、数组越界异常、除0异常等。这种通常称为未检查的异常,在虚拟机中执行时会集中处理这些异常。

(2) 其他运行中异常,通过throw语句主动抛出的异常。这类异常称为检查异常,一般是程序认为自己遇到了严重的问题,后续再运行可能会出问题,主动告知方法调用者一些必要的信息。

未检查的异常是如何让线程退出循环,执行到uncaughtExceptionHandler的?我们以除0异常来举例。

经过编译,这个触发操作最后会转化为一条div-int/lint8指令,当art虚拟机需要执行这条语句时,会去先解释这条语句,通过字符串匹配的方式找到对应的指令代码,这条语句对应的是DIV_INT_LIT8.

DIV_INT_LIT8会继续调用DoIntDivide方法。DoIntDivide方法定义如下,这里我们可以看到,当除数divisor为0的时候,虚拟机会通过hrowArithmeticExceptionDivideByZero方法抛出除零异常。

接下来的调用顺序:

Art虚拟机对该throw异常,遍历线程方法栈寄存器和PC指针寄存器, 得到函数方法调用栈等信息,最终用setException方法保存在tlsPtr变量中,等thread.destory调用dispatch uncaughtException的时候,将exception信息传递给之前RuntimeInit中注册的handler去处理。

其他的未检查的异常也是类似的逻辑。

对于检查类的异常,这一块逻辑会简单很多。通常检查类的异常都会由某段程序主动调用throw去抛出一个runntimeException,exception可以是原生的类型,例如SecurityException、NullPointerException、IllegalStateException,也可以是自己定义的某个集成于exception的类。

无论是哪种exception还是error,最终都是继承于Throwable类,throwable类在构造的时候就会调用fillInStackTrace方法。

这个jni方法最终会调用到art虚拟机的Thread:: CreateInternalStackTrace方法中。这就和刚才未检查异常里面获取stackTrace用到了同样的方法。这里会把exception(throwable)的堆栈环境记录下来。

等需要打印的时候,再通过jni方法来获取。前面提到的AMS调用handleApplicationCrash这里getStackTrace获取的就是throwable初始化时记录下来的函数运行堆栈。

5.分析方法

Java Exception的分析方法相对要简单很多,java堆栈会保留出错的调用栈,能精确到代码指定的行号。如果问题容易复现,可以直接用logcat命令复现并保存日志。如果是已经发生的低概率问题,机器现场还在的话,可以通过导出data/system/dropbox目录下的日志文件。通常是data_app_crash、system_app_crash、system_server_crash开头,以txt为后缀。通过分析日志堆栈可以快速定位到出错的代码。

例如上图的异常,堆栈可以明显看到指定的行号上存在代码运行空指针异常,并且准确的打印了空指针的变量名称,开发人员可以检查相关代码逻辑,处理即可。

扫码关注
“内核工匠”微信公众号
Linux 内核黑科技 | 技术文章 | 精选教程

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/feelabclihu/article/details/107572844

智能推荐

我用Python分析了1500家电商的销售数据,竟发现了进口车厘子的秘密_爬虫 淘宝车厘子-程序员宅基地

文章浏览阅读519次,点赞2次,收藏2次。图片来源:互联网众所周知,中国是智利车厘子最主要的出口对象,占据了其95%的市场份额。智利驻华大使馆商务参赞娜塔曾表示:“2020-2021产季车厘子实现了丰收,预计今年有50万吨左右的车厘子进入中国市场。”自2020年12月中旬开始,智利海运车厘子陆续到达中国,运输成本较此前空运方式大幅下滑。这意味着,国内消费者将能以更低的价格买到车厘子。然而,近日国内已有多地进口车厘子核酸检测结果为阳性,在这种情况下,你还敢大呼“车厘子自由”吗?01 数据获取本文利用Python采集了淘宝网1585.._爬虫 淘宝车厘子

列式存储-程序员宅基地

文章浏览阅读1.1k次。OLAP中数据存储的问题OLAP 需要队列进行选择,行式存储按行存数据,使用索引加快对数据的查找(索引包括聚集索引(表记录的排列顺序与索引的排列顺序一致)和非聚簇索引(非聚集索引指定了表中记录的逻辑顺序,但记录的物理顺序和索引的顺序不一致))。这种方式对按列的存储和检索不是很高效,查询某一列数据需要将所有行的数据扫描一次,而且对统计分析也不友好。列式存储原理若使用列式存储可以只用扫描出需要的列,行、列存储的对比。文件格式parquet 文件格式:如下图所示:parquet file = hea_列式存储

C语言字符串详解-程序员宅基地

文章浏览阅读4.3w次,点赞184次,收藏1.2k次。我们可以把字符串储存在char类型的数组中,如果char类型的数组末尾包含一个表示字符串末尾的空字符\0,则该数组中的内容就构成了一个字符串因为字符串需要用\0结尾,所以在定义字符串的时候,字符数组的长度要预留多一个字节用来存放\0,\0就是数字0例如。_c语言字符串

vue3常用自定义指令封装v-permission,按钮权限控制,添加防抖节流_vue3 v-permission-程序员宅基地

文章浏览阅读2k次,点赞8次,收藏15次。后台管理项目免不了要做权限控制,常见的分为路由级别和按钮级别,在此主要针对于按钮权限,比如说某个用户或者角色对数据有没有增删改查的权限,例如以下功能,巡查人员可以点击导入和新建,而一般用户只能选择下载模板。在 directives文件夹下分别创建permission、debounce、throttle三个ts文件,分别用于存放权限控制,防抖和节流的业务逻辑,结构清晰,方便维护以及更低的耦合度。在index.ts文件中分别导入每个自定义指令对象,再遍历注册每一个指令。_vue3 v-permission

maven-dependency-versions-check-plugin, Maven 插件查找依赖版本冲突-程序员宅基地

文章浏览阅读553次。1、maven-dependency-versions-check-plugin, Maven 插件查找依赖版本冲突转载于:https://www.cnblogs.com/yixiu868/p/11583582.html_maven-dependency-versions-check-plugin

easyUI中datagrid单选时复选框不取消问题_easyui datagrid 设置singleselect:true 后不能取消选择-程序员宅基地

文章浏览阅读472次。圈起来的地方是需要注意的这里的代码可以多选,也可以把多选的复选框全部取消勾选如果singleSelect改为true,就不能取消勾选,至于为什么,我还没弄懂(笑哭)_easyui datagrid 设置singleselect:true 后不能取消选择

随便推点

前后端分离之Spring Security Api验证实践-程序员宅基地

文章浏览阅读1.3k次。前后端分离之Spring Security Api验证实践为什么需要RESTful重定向问题为什么需要RESTful使用RESTful之前,会发现各种奇葩的url命名,对url的功能经常需要结合源代码来确认,让人头痛,使用RESTful规范之后,很多问题得以解决。仅仅依靠URL和Method就能定为功能。重定向问题需要重新定义逻辑(JDK8推荐使用Lambda表达式)登录 ,默认下..._spring security api

图像处理之常见二值化方法汇总-程序员宅基地

文章浏览阅读10w+次,点赞25次,收藏117次。图像处理之常见二值化方法汇总图像二值化是图像分析与处理中最常见最重要的处理手段,二值处理方法也非常多。越精准的方法计算量也越大。本文主要介绍四种常见的二值处理方法,通常情况下可以满足大多数图像处理的需要。主要本文讨论的方法仅针对RGB色彩空间。 方法一:该方法非常简单,对RGB彩色图像灰度化以后,扫描图像的每个像素值,值小于127的将像素值设为0(黑色),值大于等于12_二值化

GUI程序开发_gui开发-程序员宅基地

文章浏览阅读1.9k次。JAVA程序设计与应用开发(第2版)——《GUI清华大学出版社》_gui开发

PYTHON实训总结及体会500字,PYTHON实训总结思考建议_python实验体会-程序员宅基地

文章浏览阅读491次。大家好,给大家分享一下PYTHON实训总结及体会1500字,很多人还不知道这一点。这将使你在做实验时的难度加大。然后两下子就将实验报告做完。但学到的知识与难度成正比。一定要将课本上的知识吃透。【篇一:实验心得体会】就像以前做物理实验一样。在老师讲解时就会听不懂。你要清楚电桥的各种接法。这将使你极大地浪费时间。在做测试技术的实验前。因为这是做实验的基础。_python实验体会

ADC参数详解_adc电流电压零漂值-程序员宅基地

文章浏览阅读9.6k次,点赞9次,收藏117次。特性或指标总述本文将从以下特性进行简单的叙述。结合了《ADC设计基础》和TI的一些教学视频。分辨率转换误差转换速度采样率奈奎斯特采样准则混叠和抗混叠滤波器DNLINL热噪声谐波失真THDSNRENOBSFDRIMD孔径抖动孔径延迟奈奎斯特区补充分辨率一般ADC都说注明是8bit,16bit或者是24bit。这里的数值也就是分辨率的意思。分辨率是衡量A..._adc电流电压零漂值

服务器阵列卡缓存显示错误,服务器阵列卡(缓存)-程序员宅基地

文章浏览阅读1.1k次。RAID卡介绍:提到RAID卡就不得不提到什么是RAID。RAID是英文Redundant Array of Independent Disks的缩写,翻译成中文即为独立磁盘冗余阵列,或简称磁盘阵列。简单的说,RAID是一种把多块独立的硬盘(物理硬盘)按不同方式组合起来形成一个硬盘组(逻辑硬盘),从而提供比单个硬盘更高的存储性能和提供数据冗余的技术。组成磁盘阵列的不同方式成为RAID级别(RAID..._把raid缓存强制开启 显示参数无效

推荐文章

热门文章

相关标签