Android Camera 内存问题剖析,Android开发者必看避坑指南

深入分析


排查 Finalize 执行

虽然上述分析验证了问题,也证实了之前的猜想,但仍未找到导致此类问题的深层次原因,对于最终解决此类问题也仍然束手无策。为什么会有这么多的 CameraMetadataNative 对象等待执行 finalize 方法或许是下一步的调查方向。做过 Java 稳定性治理的同学应该都知道一类很有名的 TimeoutException 异常,这类异常的根本原因是 finalize 执行超时导致的,这个 case 会不会是某个对象的 finalize 执行超时导致的?

结合 FinalizerDaemon 的源码可以看到,每执行一个对象的 finalize 方法时,都会通过finalizingObject属性记录当前的对象。如果真的是 finalize 超时导致的,一定存在 finalizingObject 属性不为空的现场。我们在遍历完所有相关内存快照里的 FinalizerDaemon 线程状态后发现,这些现场的 finalizingObject 属性均为空。这个结果很意外,似乎并不是某个对象的 finalize 方法执行超时导致的。

通过分析finalizingReference = (FinalizerReference<?>)queue.remove() 发现这行代码后面的逻辑并没有对  finalizingReference 判空,说明这个地方一定不会返回空。既然不为空, queue.remove() 只能 block 等待,这个 ReferenceQueue.java 的源码也证实了猜想。

源码显示 goToSleep 是个同步方法,可能会 block。但遍历所有相关快照发现所有的 needToWork 属性均是 false,证明已经走过(只有FinalizerWatchdogDaemon.INSTANCE.goToSleep() 会置为 false,而且这个函数是 private 的,只在 FinalizerDaemon 线程里调用),所以 block 在这里的可能性几乎没有。

其实 block 在这里的原因通常是因为只有在 GC 时才会将需要执行 finalize 的对象加入到 FinalizerDaemon 的队列里。如果一段时间内没有 GC,且队列就为空时,上面的 remove 会一直 block,直到 GC 后才有对象加入到这个队列里。巧合的是我们在发生这类 native OOM 时会通过 Tailor 主动 dump Java 堆的内存快照,而 dump 快照时会触发 GC & suspend,这个最终导致大量的 CameraMetadataNative 对象被同时加入到 FinalizerDaemon.queue 的队列里。

分析 GC 策略

通过上述分析可知如果不是 GC,这些对象是不会被被加入到 FinalizerDaemon.queue 里的,这说明这类 native OOM 发生前的一段时间内一直没有 GC,才导致大量 CameraMetadataNative 对象没有及时执行 finalize,进而发生 native OOM。以上分析也在线下进入到拍摄页后静置观察实验中得到验证,这其中大概每隔 30s-40s 甚至更长时间 Java 堆才会主动触发一次 GC,在这期间 native 内存会不断增长,直到 GC 后才会大幅下降,Java & Native 内存才会恢复到正常水平。虽然问题不是 block 在 finalize 环节,但最终这个问题的原因被锁定在了 GC 逻辑上!

了解 GC 的同学可能会知道 ART 虚拟机的 GC cause 有很多种,kGcCauseForAlloc/kGcCauseBackground 是虚拟机最易频繁触发的。当停留在拍摄页不做任何操作时,程序逻辑相对简单,这期间只有相机服务周期(>=30 次/s)地通过 binder 在应用端触发创建 CameraMetadataNative 对象,并在拍摄页显示一张相机采集到的图像。这个过程 Java 堆只有 CameraMetadataNative 对象创建,而 CameraMetadataNative 自身占用内存比较小,一次 GC 之后 Java 堆内存比较富裕的情况下,虚拟机很长一段时间内不会主动触发 GC。如果这期间 native 内存的增幅过大,在下次 GC 之前触顶就发生 native OOM

综上,这类 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 内存。 [图片上传中…(image-98dc21-1615988565752-1)]

线下实验也可以发现,开启主动回收策略后,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 内存分配等),这也是很多拍摄过程中相机黑屏问题的根本原因。该方案也不经意间解决了长期存在的拍摄时相机黑屏的疑难问题。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

img
img

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
img

结尾

最后小编想说:不论以后选择什么方向发展,目前重要的是把Android方面的技术学好,毕竟其实对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

高级UI,自定义View

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。

不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

义View**

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。

不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

[外链图片转存中…(img-huAVuZnu-1711536762923)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值