记录一次 OOM 问题
本次的 OOM
问题分析是通过 Profiler
dump Java
堆内存来分析,废话少说直接开始。
Bitmap 问题
我们看到这里的 Bitmap
光 Native
内存都占用了 93MB,有 284 个实例,我这里要说明一下在 Android 8
及其以上的版本,Bitmap
的内存占用都移动到了 Native
,减少了 JVM
内存的压力,反之在 Android 8
以下的版本中由于 Bitmap
的内存是直接分配在 JVM
堆上,很大的程度上增加了 JVM
OOM
的风险。好了继续分析主要是哪部分的 Bitmap
占用内存过大。
超大的资源文件图片
这里发现一个超大的资源文件背景图,占用内存 4MB,它是用在登陆页面的背景,别的地方倒是没有用到。我曾经还遇到过比这个图更大的,直接在系统加载资源图片的时候就直接崩溃了😂。
-
解决方案1
UI 给的背景图分辨率比较大,可以考虑降低它的分辨率。 -
解决方案2
Android
系统中的资源图片是全局通用的,当第一次加载成功后后续别的地方再加载就直接用这个缓存,当然你这个资源文件非常大而且只有这一个地方使用是可以考虑使用完了后就不再缓存,手动把它直接回收掉,节约内存。
SVGA 动画产生大量 Bitmap
我这里简单介绍一下 SVGA
动画,它的原理其实就是和 Android
中的帧动画一样的,就是由一帧一帧的 Bitmap
构成,所以大家都说 Android
帧动画要谨慎使用,主要也是内存问题,同样的 SVGA
也要谨慎使用,在我们公司的 1v1
陌生人社交的项目有设计用户的动态头像框,当在聊天列表的页面就会显示每个用户头像和动态头像框,这个动态头像框就是用的 SVGA
动画,而且同时播放的动画可不止一个动态头像框,而是列表中所有可见用户的动态头像框,它不仅是有造成 OOM
的风险,而且会频繁创建和回收 Bitmap
,让内存占用成锯齿状,可谓是罪行累累。
SVGAVideoEntity
对象中持有了这些 Bitmap
的引用,而且实例的数量达到了 95 个。
-
解决方案1
最好的方案当然是弃用SVGA
动画,换其他通过CPU
动态计算的动画,减小内存的占用。 -
解决方案2 最大程度减小
SVGA
动画的分辨率,反正都是头像框显示的区域比较小。 -
解决方案3 在
Android 8
以下的设备上或者低端设备上禁止使用SVGA
动画。
Fresco 加载的网络图片
我自己没怎么使用过 Fresco
加载网络图片,用 Glide
和 Picasso
要多一点,通过 dump
内存的记录我发现它会把 Bitmap
的引用都保存在一个静态变量中。
这里持有了 172 个 Bitmap
的实例。
上面 Class
的 sLiveObjects
就是以下的静态对象:
我没有了解过 Fresco
管理内存的方式,一个页面中肯定用不了 172 个 Bitmap
,我认为这里是可以优化的,可以回收部分 Bitmap
,而且在 Fresco
加载的网络图片中有不少的内存占用都是超过了 1MB。
网络图片和动画就占用了 267 个 Bitmap
实例,总共才 284 个实例。
-
解决方案1
改用Glide
图片加载,同时配置保守的内存缓存机制。 -
解决方案2
根据不同的手机性能选择加载不同分辨率的网络图片。
内存泄漏
这个泄漏的 IMMessageListActivity
就是 IM
的聊天列表 Activity
,它还是非常重的,其中引用了不少的高内存占用对象,但是它发生了泄漏,它的泄漏是由于做了一个异步操作,然后这个异步的 Lambda
中还对这个 Activity
有引用,而这个异步的 Lambda
对象通过强引用保存在一个静态的 HashMap
中,当 Activity
销毁后,对应的对象也没有办法得到回收,因为 Activity
被静态的 HashMap
强引用,这样就导致了泄漏。
- 解决方案
静态的HashMap
不要以强引用的方式保存Callback
对象,而使用弱引用。
字符串内存占用
我们大致看 char[]
和 String
来计算字符串内存占用,大概在 30MB。
SVGA 动画描述文件
SVGA
真的是,每次扫黄都有你😂。动画的描述文件占用的内存大小也不少,我没有具体统计,但是至少也是几 MB 的内存占用。
IM 对话
最终 String
对象的引用最后找到了 ConversationEntity
中的 rawString
的变量中,ConversationEntity
最终保存在静态对象中,而且永远不会回收😂。ConversationEntity
有 9125 个实例,我按一个实例平均占用 1.5KB 内存,光 ConversationEntity
就占用 13MB 的内存(我感觉实际是大于这个值的),其实我觉得大部分的 String 的内存都是 IM 和 SVGA 占用的。
为什么有这么多 ConversationEntity
对象呢?这个是在外部的 IM 列表页面中就把对应的 ConversationEntity
就加载出来了,讲道理我们应该是在二级的聊天页面中才加载,而且退出聊天页面后这个数据就该释放,但是由于历史原因没人敢动这部分代码,产品也是无脑的堆需求,压根儿就不会有人管这个,也就是常说的 Shit Mountain
,我相信不只是我们的公司有这样的问题。
最后
根据我们的教训,希望大家也不要踩这样的坑,如果已经在坑中了,那就想想如何爬上来吧。