工作上遇到 android app 意外发生 Bitmap 内存泄漏的问题。把 vm heap dump 下来一看不得了,有大量 byte[] 数组驻留在 vm 内存内,但却没有被任何对象引用到!明明我们的 app 内存不足到都崩溃了,gc 还不回收这些垃圾 byte[],这是为什么?而且这个现象在 android 6.0 上发生,但在 android 7.1 和 8.0 上都不会发生。而且,在我们的 app 退出那个会导致这个内存泄漏的 view 之后,gc又可以正确回收那些幽灵数组了。总觉得,好神秘!
稍微读了一下各个版本 android 的 Bitmap 的 java 和 jni 代码之后,我觉得这是因为内存虽然被标记为虚拟机内内存,但是创建和释放都是由 native 层管理着,而 native 层出于某些原因,不愿意及时释放这批内存所导致的。是一起有组织有预谋的私藏内存事件。
怎么办呢,native debugger attach 上,直接调试 native 层吧,没有 symbols,看不到源码和变量,只能看到进入了哪个函数和任意时刻寄存器的数值,这调试起来真是两眼一抹黑,事倍功半。
想要有源码调试?可以啊,自己编译 aosp (android open source project),用自己编译的 system.img 启动模拟器就可以有源码调试了。于是花了一个星期各种式错后终于用上了自己的 system.img,自豪!结果发现用我的 system.img 之后内存就不泄漏了。耍我哦!
细致调查后发现不仅替换整个 system.img 会这样,就算只是替换掉 android 主机(模拟器)内的 /system/lib/libandroid_runtime.so 为我编译的版本,内存泄漏问题也会消失。这个 .so 里大概都是 android jni 相关的东西,当然我怀疑的 Bitmap native 部分的实现也在里面,所以我的推理方向似乎是正确的。只是没法继续直球出击分胜负了,遗憾。
顺便说一下,用 android studio 的 android profiler 的 memory 面板查看就会发现,用了我的 .so 之后 app 的 native memory 使用量始终非常低,而 原版模拟器会在 load 一个 Bitmap 后 java memory 和 native memory 会一起增减。目前我初步怀疑是我编译的版本没有能够正确访问硬件相关功能导致硬件加速部分产生了差别,但还没有什么证据。
如果是兴趣项目,我肯定会继续研究 aosp 的,可惜这次是工作,不能太任性。下一步我准本静态分析 Bitmap 相关的代码,配合半瞎的 native debugger 和试错性调整相关的 react-native 部分代码来找出避免这一内存泄漏的方法。
之前遇到过 react-native 的圆角图片导致的 android 7.1 以下的 app 会发生 graphics memory 内存无限泄漏的问题,最后也是在没理解根本原因的情况下被我绕过去了,相信这次也能逢凶化吉!