OOM
OOM(Out Of Memory)是 Android 应用开发中相信每个人都遇到过的问题, 而 OOM 在 crash log 中的 stack trace 一般没有实际意义, 因为是在分配内存的时候才会抛出 OOM 异常, 而这个时候的 stack trace 和 OOM 的原因没有任何关系. 所以 OOM 问题的定位和分析就需要多花费一些功夫.
下面, 我就结合一个例子, 来讲讲怎么定位 OOM 问题.
问题
在程序员们把代码写完, 基本流程测试无误, 准备要发布的时候, 云测的结果却是: 一大波 OOM 异常. 没办法, 只好重新打开电脑定位问题.
由于不是我一个人写的代码, 所以直接看代码定位问题有点困难, 这个时候就要上工具了.
1. 定性问题
1.1 定位问题, 先定性
这是一个内存泄漏导致的 OOM, 还是应用本身设计不当, 导致一次需要加载的内存过多, 导致的 OOM?
定位问题的方法很简单, 看内存是不是一直在增长, 如果在使用的过程中内存一直在增长, 则很有可能是内存泄漏导致的.
1.2 推荐使用的工具
Android Studio 自带的 Memory Monitor, 很简单的一个小工具, 能够把应用内存实时展现出来, 简单到我认为不需要多说了. 如下图:
在摆弄了几分钟之后, 我发现了几个问题:
1. App 刚刚启动的时候, 内存占用就很大, 因为用了很多的贴图(设计不当)
2. App 在进入播放界面并且推出之后, 即使什么操作都不做, 内存一直在缓慢增长(内存泄漏)
3. App 在我退出再进的时候, 内存占用几乎翻番(内存泄漏)
其中, 问题 2 很快就能猜出来, 播放结束后 MediaPlayer 没有被释放, 之后验证了下, 解决.
2. 设计不当? 优化!
这个问题就很泛了, 比如, 纯色的背景就不用图片来实现, 不用超过需要像素值的图片, 不加载显示范围之外的图片, 等等.
最终找到了几张图片, 只需要 1280x720, 给的图是 1920x1080. 同时简单实现了图片的 LazyLoading. 问题 1 基本凑合搞定.
3. 内存泄漏?
如果在使用过程中, 内存曲线一直是上涨趋势, 这就很有可能存在内存泄漏了.
3.1 查看堆的信息
Android SDK 的工具集中就提供这样一个工具: Device Monitor. 使用方法如下:
1. 打开 Android Device Monitor
2. 选择你要调试的进程
3. 点击 Update Heap 按钮
基本情况如下图:
可以看到基本的堆情况, 以及堆内对象的概览.
3.2 查看 Activity 泄漏
常见的内存泄漏很多都是由于 Activity 对象不能被释放导致的, 用下面的 adb 命令可以快速的定位到这个问题:
adb shell dumpsys meminfo <package_name>
得到结果如下:
- ** 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 数量很值得关注.
关于其他的输出含义, 见 developer docs
3.3 取 heap dump
在基本确定内存有泄漏之后, 就需要定位具体是哪个对象泄漏, 好定位相关代码, 这个时候就可以对 heap dump 进行分析.
heap dump 就是一个内存 heap 的快照, 可以用来很具体的分析内存里到底有什么.
首先, 我们用 Device Manager 需要导出一个 heap dump, 如图所示
然后转化成 java dump, 用 Eclipse Memory Analyzer Tool (MAT) 来分析.
用 platform-tools 里的 hprof-conv 来转化:
hprof-conv heap-original.hprof heap-converted.hprof
转化完成之后, 我们就可以用 MAT 来打开分析.
4. 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 内容, 那么找起来会方便很多. 下面是一个查看的方法:
Stack Overflow 上回答见这里
结尾
整个过程我基本是参照 Google 官方文档 Investigating Your RAM Usage, 可以读下这份文档.
最后
如果你看到了这里, 觉得文章写得不错就点个赞呗? 如果你觉得那里值得改进的, 请给我留言. 一定会认真查询, 修正不足. 谢谢.
最后针对 Android 开发的同行, 小编这边给大家整理了一些资料, 其中分享内容包括但不限于 [高级 UI, 性能优化, 移动架构师, NDK, 混合式开发(ReactNative+Weex) 微信小程序, Flutter 等全方面的 Android 进阶实践技术] 希望能帮助大家学习提升进阶, 也节省大家在网上搜索资料的时间来学习, 也是可以分享给身边好友一起学习的!
为什么某些人会比你优秀, 是因为他本身就很优秀还一直在持续努力变得更优秀, 而你是不是还在满足于现状内心在窃喜! 希望读到这的您能转发分享和关注一下我, 以后还会更新技术干货, 谢谢您的支持!
来源: http://www.jianshu.com/p/c24ba4fff691