
文章插图
综上,这类 native OOM 的根本原因是:当应用自身的 native 内存本身已处于高水位时,开启相机后,相机服务会持续通过 binder 通信在应用侧创建 CameraMetadataNative 对象,创建 CameraMetadataNative 对象的同时也会在应用侧通过 jni 接口在 native 层创建/复用一块存放 camera_metadata_t 的相对比较大的内存 。由于 Java 层的 CameraMetadataNative 对象本身比较小,这种连续创建小对象的行为一定时间内很难触发 Java 层的 GC,导致其间接引用的 native 内存不断上涨,最终触发虚拟内存上限而 crash 。
解决思路问题的原因虽然相对比较简单,但如何解决这类问题还是比较难抉择的 。既然是 GC 不及时导致的,一种简单的方案就是在拍摄页周期性触发 GC 。但如果 GC 间隔比较小,GC 毕竟是耗时的,GC 过于频繁会严重影响拍摄体验;如果 GC 间隔时间比较长,还是会有大概率重蹈这类 native OOM 的覆辙 。
主动触发 GC 的方案很难平衡对性能的影响 。其实问题的重点不是 Java 层,而是 Java 对象引用的 native 内存,如果及时主动释放这部分内存就可以从根本上彻底解决此类问题 。通过前面的分析可以知道,这部分内存原本是在 GC 时的 finalize 环节回收,但如果提前发现 CameraMetadataNative 不再使用时,主动触发来释放这部分内存就可以一劳永逸 。通过分析源码可以发现 CameraMetadataNative 传递到应用层之后后续并未再使用,在应用层使用完 CameraMetadataNative 对象之后,通过反射调用 close 函数即可释放其所引用的 native 内存 。

文章插图
线下实验也可以发现,开启主动回收策略后,Native 内存的增长速度比之前大幅降低 。这期间 Java 堆& native 层仍有持续增加的小对象,但 native 的增长速度远小于 Java 层了,这种场景下 Java 内存会在 native 内存触顶之前先触发 GC,而大幅降低了发生 native OOM 的可能

文章插图
最终该方案上线后,效果十分明显,此类 crash(Java & Native 总占比>15%)基本清零 。后续搜集到的内存监控日志里 CameraMetadata 相关的内存基本都在 2M 以内,效果立竿见影!
总结此类问题存在时间很久,至少从 Android 4.4 开始都是通过 CameraMetadataNative 的 finalize 函数来释放 native 内存 。过去拍摄的需求比较简单,绝大多数时候都是使用 ROM 自带的相机应用来拍照,因为这类 app 比较简单,native 内存水位本身很低,很难触发到虚拟内存的上限,所以此类问题并没暴露出来 。随着小视频等 app 的兴起,拍摄需求越来越重(特效&美颜等),app 也越来越复杂,应用自身的 native 内存水位不断上涨,加上 native 内存泄漏等原因,当长时间停留在拍摄页时,这类问题就很容易触发 。
此外,CameraMetadata 的内存分配失败时,并不会直接 crash,这个时候有其他内存分配请求时才会触发 crash(如线程创建、GL 内存分配等),这也是很多拍摄过程中相机黑屏问题的根本原因 。该方案也不经意间解决了长期存在的拍摄时相机黑屏的疑难问题 。
这类问题既有应用自身的原因,也有内存回收策略设计的原因 。应用在尽可能减少泄漏的同时,也应该努力降低自身 native 内存水位 。AOSP 里利用 Java 的 finalize 方法来释放其间接引用的 native 内存是个偷懒挖坑的设计,类似的案例在 AOSP 里比比皆是 。我们在实际开发中,类似内存这种有限的资源应及时回收,甚至可以主动限定对象的生命周期,一旦完成使命就主动回收其占用的内存,避免使用 finalize 逻辑来释放 native 内存 。
文中提高的两个工具(Native 内存监控工具 Raphael & Android 堆内存快照裁剪压缩工具)是西瓜视频 Android 团队在长期的内存优化治理中开发的两套高效实用的基础工具,在我司内部各大 app 中应用非常广泛,是内存优化&稳定性治理的绝对首选 。这两套工具我们也会在后续的监控工具建设&优化治理实践等技术文章中介绍相关技术细节,敬请关注 。
更多分享字节跳动自研线上引流回放系统的架构演进
字节跳动表格存储中的事务
iOS大解密:玄之又玄的KVO
今日头条 Android '秒' 级编译速度优化
字节跳动-西瓜视频 Android 团队字节跳动-西瓜视频 Android 团队是负责字节跳动旗下西瓜视频 App 研发的客户端团队,团队在满足业务高速迭代的同时,持续优化性能和体验,提升研发效率,探索 Flutter 等跨平台方案 。我们长期招聘业务研发、架构师、Flutter 工程师、骨干工程师、实习生,在北京、杭州、上海三地均有职位 。业务体量大,团队成长快,技术挑战大,欢迎各路人才加入!联系邮箱: tech@bytedance.com ;邮件标题:姓名-工作年限-西瓜-Android 。
推荐阅读
- 铠侠 极至光速系列内存卡评测:经典红白复刻,唯有品质依旧
- Android Jetpack架构组件Navigation管理Fragment框架
- 多进程编程 - 共享内存
- IDE 最佳Android应用程序开发工具
- 虚拟内存 和 page fault 的解释
- 对于内存结构的简单理解
- 电脑卡慢时升级内存是最低级做法,正确顺序是这样
- 高频内存对性能影响到底有几何?试试看就知道了
- Android WebView 优化梳理
- 定位Flutter内存问题很难么?
