Android内存泄漏案例分析一
来源:互联网 发布:电信网络的dns是什么 编辑:程序博客网 时间:2024/06/05 00:11
背景介绍
最近负责的Gallery模块测试时频繁报出使用一段时间后就发生Crash,通过Log查看发现是OOM引发,本篇文档记录解决该问题的分析过程。
Log分析
该OOM问题测试没有找到复现步骤,只是报出使用一段时间就会发生OOM这个非常宽泛的现象,没有复现步骤,没有确定时间节点。查看Log每次发生OOM的代码调用栈也不尽相同。贴出出现OOM的几段Log。
通过上面的几段Log,看出都是在申请一个较大的内存空间时,系统memory空间不够了,导致发生了OOM,这里都是在对图片进行操作,因此申请了较大的内存空间。
至此有一个大概的查找方向:问题出现的时机是在Gallery创建大的Bitmap对象,了解过Gallery业务流程很容易就会怀疑这个过程多半发生在PhotoPage界面,也就是浏览大图界面,但深入了解发现Gallery已经对这块进行了处理,并不是大图有多大,就一次性创建多大的Bitmap对象,而是将一个大图分成多个区域加载进来,具体可以查看Gallery代码中LocalImage类中的requestLargeImage方法,它利用的是BitmapRegionDecoder技巧,分块多次加载大图,这里不赘述。在排查Log发现问题发生在refocus操作几率大,测试也印证了该点。这里refocus是MTK平台针对Gallery做的一个新功能,它能对景深图片进行重聚焦/调整图片光圈值。排查重点放在refocus操作上。
复现问题
上述Log分析都只是逻辑上的分析,没有验证,针对这些分析需要工具帮忙确认进一步缩小排查范围。这里使用DDMS 工具分析,打开DDMS,选择Gallery所在的进程,点击Update Heap选项。切换到Heap视图边操作边查看内存变化.
留意占用空间最多的项,这里是byte array,通过在AlbumSetPage/AlbumPage来回切换滑动观察到该项值大概稳定在30M~50M之前,并且data object/class object值也没有出现一直不断的增加的现象,点击Cause GC触发垃圾回收器工作,上述这些值也有明显的回落。
在加入浏览大图的PhotoPage界面,来回在PhotoPage滚动浏览图片,观察到内存大概稳定在50~70M之间。
当返回AlbumPage界面,点击Cause GC触发垃圾回收器工作,内存回落到之前的30M~50M区间。
现在在加入refocus操作.发现进入refocus操作界面内存上升,退出后内存没有明显下降,曙光出现了。于是不停的进入退出refocus界面,大约来回二十多次后,问题复现了,logcat发现报错也是OOM,到此找到了复现问题的步骤。
MAT工具排查
有了上面的复现步骤,开始用MAT工具定性分析内存使用情况。
利用命令: adb shell am dump heap [package name] >/data/local/tmp/gallery6.hprof
生成hprof文件。
再用命令:hprof-conv gallery6.hprof gallery6_.hprof
转换成MAT工具可以识别的格式。
最终生成的hprof文件如下:
点击Dominator Tree:
可以看到这里有多个refocusView和RefocusActivity存在多个实例,这个很可能是突破口,
先排查RefocusView单击鼠标右键,选择List Objects–>with incoming references
继续选中过滤出来的RefocusView,鼠标右键,选择
Path to GC Roots–>exclude weak/soft references.
可以看到RefocusView—->ApertureSlider—->ValueAnimator的一条引用链。
同样的方式在排查RefocusActivity发现跟RefocusView相关。
再看同样可疑的Bitmap对象还是指向RefocusView.
看到这,问题基本已经有结论了:
RefocusActivity—>RefocusView—->ApertureSlider—->ValueAnimator这条引用关系链存在问题。
为了快速验证分析结论是否正确,在代码中将ApertureSlider去除。
再次检验结果如下:
可以看到没有再出现多个RefocusView/RefocusActivity/Bitmap实例了。
复测也没有在出现OOM。
代码分析
通过上面的一步步分析,我们已经找到了问题原因出在RefocusView上,那么它为什么没有GC回收呢,再次梳理下逻辑思路:
RefocusActivity--->RefocusActivity的onCreate里加载RefocusView
---->RefocusView里持有ApertureSlider--->ApertureSlider持有ValueAnimator--->ValueAnimator没有被回收
现在进入ApertureSlider查看ValueAnimator,果然一进入就发现了ValueAnimator定义存在问题.
这里声明了一个static 类型的ValueAnimator,并且在构造方法里对它设置了动画监听器。而正是这个静态类型对ApertureSlider保持了一个引用,按照上面梳理的思路,在反向推回去,最终导致了RefocusActivity无法被GC回收。找到了问题点,就好修改了,去掉 static final修饰符即可。从一个小小的变量引发内存泄漏,导致最后出现整个应用的Crash。开篇Log里提示的错误,算是正好撞在了枪口上,一次申请了较大的内存空间,将问题放大凸显了出来。
总结回顾
分析问题的思路
回过头来看,本篇的问题实在显而易见。但透过它还是能收获一些知识。
- 大胆假设:一个APP做到后期,往往代码量很大,成千上万的类文件,错综复杂的逻辑链条穿插其中。除非显而易见的问题,否则最好不要一头扎进细节处开始查询问题,先在大脑中过一遍发生问题的场景是什么?顺着脑海中的思路假设问题发生的条件。
- 小心求证:有了猜测,就需要严谨的分析验证。在这个问题中,一开始单从Log中我猜测的是创建Bitmap对象太大了,代码中直接按大图进行decode,也确实在代码中找到了生成图片时设置的采样值是1(inSimpleSize=1),这样如果面对的是一张高清大图,非常大的可能出现OOM。但验证发现调整图片采样值只是降低了OOM发生的概率,没有从根本上根除问题。于是继续猜测分析找到了问题的根本原因。
MAT中的一些概念
- outgoing references:被当前实例引用到的所有对象。通过它可以看出当前实例”抓着”哪些对象不放。
- incoming references:与outgoing references相反,表示引用到当前实例的所有对象。通过它可以看出谁在内存中”抓着”当前实例不放。
- Shallow size:对象本身占用内存的大小,不包含其引用的对象。
- Retained Heap:被一个对象所引用到的所有对象的Shallow size总和。
参考链接
- 八个造成 Android应用内存泄露的原因
- Android内存优化之一:MAT使用入门
- Android 内存泄漏总结
- http://eclipsesource.com/blogs/2013/01/21/10-tips-for-using-the-eclipse-memory-analyzer/
- https://www.jetbrains.com/help/dotmemory/10.0/Outgoing_References.html
- Android内存泄漏案例分析一
- Android内存泄漏案例分析一
- Android 内存泄漏案例分析总结(Handler)
- Android 内存泄漏案例分析总结(Handler)
- Android 内存泄漏和OOM分析(一)
- 内存泄漏分析一
- android 内存泄漏案例解析
- Android常见内存泄漏案例
- Android 内存泄漏分析
- android 分析内存泄漏
- android内存泄漏分析
- android内存泄漏分析
- android 内存泄漏分析
- Android内存泄漏分析
- Android内存泄漏分析
- android 内存泄漏分析
- Android内存泄漏分析
- android内存泄漏分析
- 数据库(第一范式,第二范式,第三范式)
- GregorianCalenda
- 构造函数与分配空间
- ARM处理器系列博客:提笔前言
- Python (二)列表、元组
- Android内存泄漏案例分析一
- OPENGL编程指南(红宝书阅读笔记)
- [PAT] A1008
- 学习VTK(1)
- poj 1265 Area
- SimpleAdapter 总结2
- struts 跳转后js css加载不了和请求求struts有问题
- 使用github和hexo搭建博客问题总结
- 需要研究的网站