工作中遇到的疑难问题--"闪退"

最近在实现一个自动化测试功能时,经常碰到"闪退"现象。这里总结下问题结果的整个过程。

分析日志

起初怀疑只是项目中的某段代码逻辑有问题,导致系统crash。所以只要找出crash原因即可:

使用命令 adb locat > crash.txt 保存完整日志,然后搜索fatalexception等关键字没有任何发现。

查看内存

既然不是代码逻辑导致crash,那有可能是内存泄漏导致内存溢出?

运行Android Studio的Profiler查看程序运行时的实时内存情况,发现自动化测试在刚启动阶段,内存占用空间有上升趋势;但是在运行过程中,内存并没有无限制上涨,反而是维持在一个稳定状态。因此打消了内存泄漏的顾虑。

线程死锁

因为这个功能是使用3方引擎对输入音频进行自动化识别(ASR),所有的这些操作都是在子线程中完成,所以怀疑是多线程并发,造成死锁导致程序"闪退" 。

本来想使用Systrace抓取CPU和线程的使用状态,但是因为问题是偶发现象,所以一直抓不到问题复现时的systrace记录。 无奈只能选择暂时放弃这条线路(后续事实证明,如果这时能坚持一下,应该能够提早发现部分问题)。

既然不能使用Systrace分析,那就简单看下 /data/trace 目录中记录的线程信息,搜索关键字“dead lock”也没有什么重要的信息,遂作罢。

重新分析log

乖乖的重新回头仔细查看Log日志,并分析"闪退"发生的时间节点前后的每一行log,终于发现有如下一行代码:process kill by signal 11

Google一番,总结下来 造成"kill by signal 11"的原因,主要有以下3点:

  1. C或者C++代码存在内存泄漏

  2. Java栈溢出

  3. FileDescriptor文件个数超出系统限制

1.C代码内存泄漏

检测C层native代码是否存在内存泄漏并不像Java 代码那么简单,我使用了LeakTracer来检测,但是在它生成的leak.out文件中并无发现。

为了再次确认这一点,同样还是使用Android Studio的Memory来查看确认运行时的native大小,结果依然是维持在固定的范围内,到这基本就能排除内存泄漏问题了。

LeakTracer这个库的使用并不简单,这个库本来是用作Linux系统上的,但是可以被移植到安卓项目中使用。网上有一篇对其使用介绍的文章 

https://my.oschina.net/wolfcs/blog/536997

2.Java栈溢出

Java的栈溢出问题很容易分析,当出现栈溢出时,一般都会伴随的Stackoverflow 的异常,所以直接在log日志中搜索Stackoverflow。正如我所预料的一样,事情并不会如此一帆风顺--果然所搜不到任何与栈相关的任何信息。

在搜索栈溢出相关文档时,偶然遇到一篇关于C++代码栈溢出的文章,感觉比较有意思,C++对于参数在函数间传递的方式和Java不太一样,日后可以再研究研究

https://xionghengheng.github.io/2019/01/06/一个有意思的栈溢出crash

3.FileDescriptor文件个数溢出

Linux系统每个进程对于FD都会有一个最大个数限制,一般情况下是1024个。Linux或者Mac系统中可以使用 lsof 命令查看FD的个数。

接下来,每隔5s执行如下命令,查看当前进程实时的FD个数,

adb shell lsof | grep <进程ID> | wc -l

发现我创建的进程中的FD个数一直处于增长状态,当个数到达1024之后进程瞬间被kill,终于找到问题原因。

剩下的就是在代码查找原因,造成FD溢出的原因一般是因为代码中使用的各种Stream没有关闭。因为工程代码量不大,所以使用grep命令,全局搜索项目中所有使用Stream的地方,果然发现有未释放Stream,解掉即可。

线程假死

修复bug之后,跑了几次自动化测试,诚然没有发现"闪退"问题。本以为大功已经告成,刚想感叹一下问题解决不易,"闪退"又忽然出现在眼前。

估计近1年我都不会忘记当时我楞在那呆呆的盯着屏幕足足30s。就如生活给了我一巴掌,扇的我有点怀疑人生。

难道还有Stream没有释放?再三检查代码,确认都已关闭。只能说明代码还存在其它原因导致"闪退",但是索性这会儿出现的"闪退"现象都有规律:进程在被kill之前都会处于一段时间的假死,当我切换到后台之后,在不定时间内就会被系统杀死。

并且当我再次使用Android Studio查看进程实时内存状态时,发现当进程被杀死时,native code占用内存瞬间暴增。各种迹象表明应该是多线程的问题,因此接下来我将精力集中到多个线程并发的问题,最终排查出以下代码:

上述代码主要是在自动化识别一次失败后,尝试自旋3次,重复识别。如果3次都为fail,则最终结果是fail状态。但是因为代码逻辑的问题此段代码在多线程情况下,会出现死循环,最终导致识别引擎中的native code内存暴增。

将3次自旋注释之后,多次尝试终于不再复现"闪退"问题,大功告成!

如果你喜欢本文

长按二维码关注

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页