博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一次解决OOM的经历
阅读量:5918 次
发布时间:2019-06-19

本文共 4083 字,大约阅读时间需要 13 分钟。

OOM

OOM(Out Of Memory)是Android应用开发中相信每个人都遇到过的问题,而OOM在crash log中的stack trace一般没有实际意义,因为是在分配内存的时候才会抛出OOM异常,而这个时候的stack trace和OOM的原因没有任何关系。所以OOM问题的定位和分析就需要多花费一些功夫。

下面,我就结合一个例子,来讲讲怎么定位OOM问题。

问题

在程序员们把代码写完,基本流程测试无误,准备要发布的时候,的结果却是:一大波OOM异常。没办法,只好重新打开电脑定位问题。

由于不是我一个人写的代码,所以直接看代码定位问题有点困难,这个时候就要上工具了。

0 定性问题

0.1 定位问题,先定性

这是一个内存泄漏导致的OOM,还是应用本身设计不当,导致一次需要加载的内存过多,导致的OOM?

定位问题的方法很简单,看内存是不是一直在增长,如果在使用的过程中内存一直在增长,则很有可能是内存泄漏导致的。

0.2 推荐使用的工具

Android Studio自带的Memory Monitor,很简单的一个小工具,能够把应用内存实时展现出来,简单到我认为不需要多说了。如下图:

Memory Monitor

在摆弄了几分钟之后,我发现了几个问题:

  1. app刚刚启动的时候,内存占用就很大,因为用了很多的贴图(设计不当)

  2. app在进入播放界面并且推出之后,即使什么操作都不做,内存一直在缓慢增长(内存泄漏)

  3. app在我退出再进的时候,内存占用几乎翻番(内存泄漏)

其中,问题2很快就能猜出来,播放结束后MediaPlayer没有被释放,之后验证了下,解决。

1 设计不当?优化!

这个问题就很泛了,比如,纯色的背景就不用图片来实现,不用超过需要像素值的图片,不加载显示范围之外的图片,等等。

最终找到了几张图片,只需要1280x720,给的图是1920x1080。同时简单实现了图片的LazyLoading。问题1基本凑合搞定。

2 内存泄漏?

如果在使用过程中,内存曲线一直是上涨趋势,这就很有可能存在内存泄漏了。

2.1 查看堆的信息

Android SDK的工具集中就提供这样一个工具:Device Monitor。使用方法如下:

  1. 打开Android Device Monitor

  2. 选择你要调试的进程

  3. 点击Update Heap按钮

基本情况如下图:

image

可以看到基本的堆情况,以及堆内对象的概览。

2.2 查看Activity泄漏

常见的内存泄漏很多都是由于Activity对象不能被释放导致的,用下面的adb命令可以快速的定位到这个问题:

adb shell dumpsys meminfo 

得到结果如下:

** MEMINFO in pid 9953 [com.google.android.gm] **                 Pss     Pss  Shared Private  Shared Private    Heap    Heap    Heap               Total   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free              ------  ------  ------  ------  ------  ------  ------  ------  ------  Native Heap      0       0       0       0       0       0    7800    7637(6)  126  Dalvik Heap   5110(3)    0    4136    4988(3)    0       0    9168    8958(6)  210 Dalvik Other   2850       0    2684    2772       0       0        Stack     36       0       8      36       0       0       Cursor    136       0       0     136       0       0       Ashmem     12       0      28       0       0       0    Other dev    380       0      24     376       0       4     .so mmap   5443(5) 1996    2584    2664(5) 5788    1996(5)    .apk mmap    235      32       0       0    1252      32    .ttf mmap     36      12       0       0      88      12    .dex mmap   3019(5) 2148       0       0    8936    2148(5)   Other mmap    107       0       8       8     324      68      Unknown   6994(4)    0     252    6992(4)    0       0        TOTAL  24358(1) 4188    9724   17972(2)16388    4260(2)16968   16595     336 Objects               Views:    426         ViewRootImpl:        3(8)         AppContexts:      6(7)        Activities:        2(7)              Assets:      2        AssetManagers:        2       Local Binders:     64        Proxy Binders:       34    Death Recipients:      0     OpenSSL Sockets:      1 SQL         MEMORY_USED:   1739  PAGECACHE_OVERFLOW:   1164          MALLOC_SIZE:       62

其中的ViewRootImpl、Activities、AppContexts数量很值得关注。

关于其他的输出含义,见

2.3 取heap dump

在基本确定内存有泄漏之后,就需要定位具体是哪个对象泄漏,好定位相关代码,这个时候就可以对heap dump进行分析。

heap dump就是一个内存heap的快照,可以用来很具体的分析内存里到底有什么。

首先,我们用Device Manager需要导出一个heap dump,如图所示

导出heap dump

然后转化成java dump,用 (MAT) 来分析。

用platform-tools里的hprof-conv来转化:

hprof-conv heap-original.hprof heap-converted.hprof

转化完成之后,我们就可以用MAT来打开分析。

3 Eclipse Memory Analyzer Tool (MAT)

当我们用MAT打开dump时,看到的基本界面如下:

这里有几个概念需要了解:

  1. Shallow Heap & Retained Heap

    • Shallow Heap的大小就是对象所占的内存空间,一般一个Object持有一个引用会需要32或者64bit的空间(取决于JVM)。

    • 由于A对象持有B对象引用会导致B对象在GC中不会被销毁,所以由于被对象直接或者间接持有引用而不会被释放的对象的占用的内存总和,就是Retained Heap。

    • 简单来说,Shallow heap就是对象占用的空间,Retained Heap就是假如对象被释放,连带能够释放出来的空间。

  2. Dominator Tree

    • 定义在。简单来说,Dominator Tree可以很好地观察Retained Heap大小。

    • 通常,在dump中查看Histogram和dominator_tree就可以看出一些端倪,例如这个例子中,histogram图中,占用内存最多的是byte[]对象,通过右键 -> List object菜单,可以看到

基本上都是Bitmap对象,而且Bitmap有重复数据:

到这一步,可以继续追查是谁导致两份相同的数据不能得到释放,通过

右键 -> Path to CG root 功能,可以追查到最终是被哪个对象持有导致不能被释放,结果如下:

到这里,问题基本就明白了:

DBHelper是一个单例静态对象,这个对象会持有一个Context对象,在代码中被当成Context传入DBHelper的是一个Activity对象,所以这个Activity不会被释放;当这个Activity被销毁重建时,新的Activity会重新加载ContentView,而老的Activity所持有的整个View的树全部不会被释放,同时View持有的图片也不会被释放,导致内存不够。

至此,整个问题基本已经找到,同时告诉我们,用单例最好不要持有Context对象,如果需求需要,查看下这个设计是否合理,以及使用的时候谨慎。

一个常用的MAT技巧

在分析内存占用的时候,通常可以看到各种占用最多的时Bitmap对象,这个时候,如果能直接显示这个Bitmap内容,那么找起来会方便很多。下面是一个查看的方法:

结尾

整个过程我基本是参照Google官方文档 ,可以读下这份文档。

转载地址:http://bnbvx.baihongyu.com/

你可能感兴趣的文章
Python写的Web spider(网络爬虫)
查看>>
Android之Toast通知的几种自定义用法
查看>>
zimbra邮件搭建
查看>>
kvm虚拟化学习笔记(十五)之kvm虚拟机动态迁移
查看>>
使用eval和loop在模版中直接读取数据库内容并输出
查看>>
云计算开源英雄集结令!五项定制大奖等你来战
查看>>
RIP
查看>>
yum安装报错
查看>>
python之万维网
查看>>
LAMP 环境搭建实例
查看>>
解决MySQL不支持InnoDB
查看>>
eclipse和maven创建WebApp项目
查看>>
jquery的attr和prop区别之实例
查看>>
grep小练习
查看>>
隆文互动营销研究院服务的企业微博再创新高
查看>>
部署Windows RDS服务
查看>>
ZigBee Silicon Labs/Ember EFR32MG 2.9 Mighty Gecko开发套件开箱
查看>>
JAVA常见算法题(十一)
查看>>
网络基础CCNP|OSPF(7)
查看>>
log.51cto.com/imp_countall.php 文件出错
查看>>