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
0 0
原创粉丝点击