图片兜底针对因 activity、fragment 泄漏导致的图片泄漏,我们在 onDetachedFromWindow 时机进行了监控和兜底,具体流程如下:
文章插图
图 17. 图片兜底流程
图片监控关于对不合理的大图 or 图片使用我们在字节码层面进行了拦截和监控,在原生 Bitmap or 图片库创建时机记录图片信息,对不合理的大图进行上报;另外在 ImageView 的设置过程中针对 Bitmap 远超过 view 本身超过大小的场景也进行了记录和上报 。
文章插图
图 18. 图片字节码监控方案
更多思考是不是解决了 OOM 内存问题就告一段落了呢?作为一只追求极致的团队,我们除了解决静态的内存占用外也自研了 Kenzo(Memory Insight)工具尝试解决动态内存分配造成的 GC 卡顿 。
Kenzo 原理Kenzo 采用 JVMTI 完成对内存监控工作,JVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 native 编程接口 。JVMTI 开发时,应用建立一个 Agent 使用 JVMTI,可以使用 JVMTI 函数,设置回调函数,并从 Java 虚拟机中得到当前的运行态信息,并作出自己的业务判断 。
文章插图
图 19. Agent 时序图
Jvmti SetEventCallbacks 方法可以设置目标虚拟机内部事件回调,可以根据 jvmtiCapabilities 支持的能力和我们关注的事件来定义需要 hook 的事件 。
Kenzo 采用 Jvmti 完成如下事件回调:
- 类加载准备事件 -> 监控类加载
- ClassPrepare:某个类的准备阶段完成 。
- GC -> 监控 GC 事件与时间
- GarbageCollectionStart:GC 启动时 。
- GarbageCollectionFinish:GC 结束后 。
- 对象事件 -> 监控内存分配
- ObjectFree:GC 释放一个对象时 。
- VMObjectAlloc:虚拟机分配一个对象的时候 。
Kenzo 整体分为两个部分:
生产端
- 采集内存数据
- 以 sdk 形式集成到宿主 App
- 处理生产端的数据
- 输入 Kenzo 监控的内存数据
- 输出可视化报表
文章插图
图 20. kenzo 框架
生产端主要以 Java 进行 API 调用,C++完成底层检测逻辑,通过 JNI 完成底层逻辑控制 。工作流
消费端主要以 Python 完成数据的解析、视图合成,以 HTML 完成页面内容展示 。
文章插图
图 21. kenzo 框架
可视化展示
文章插图
图 22. kenzo 聚合展示
启动阶段内存归因基于动态内存监控我们对最为核心的启动场景的内存分配进行了归因分析,优化了一些头部的内存节点分配:
文章插图
图 23.启动阶段内存节点归因
另外我们也发现启动阶段存在大量的字符串拼接操作,虽然编译器已经优化成了 StringBuider append,但是深入 StringBuider 源码分析仍在存在大量的动态扩容动作(System.copy),为了优化高频场景触发动态扩容的性能损耗,在 StringBuilder 在 append的时候,不直接往 char[] 里塞东西,而是先拿一个 String[] 把它们都存起来,到了最后才把所有 String 的 length 加起来,构造一个合理长度的 StringBuilder 。通过使用编译时字节码替换的方式,替换所有 StringBuilder 的 append 方法使用自定义实现,优化后首次安装首页 Feed 滑动 1min 的 FPS 提升 1 帧/S,非首次安装启动,滑动 1min 的 FPS 提升 0.6 帧/S 。
【抖音 Android 性能优化系列:Java 内存优化篇】
推荐阅读
- 使用GPU.js改善JavaScript性能
- 高性能负载均衡 DPVS 的 SNAT 功能介绍
- 聊聊CDN与高性能流媒体服务器的关键技术设计
- 充分榨干 CPU 的每一个 TICK:软件性能优化方法知多少
- 安卓|Android 14首曝:翻转蛋糕
- Android Jetpack 架构浅析
- 使用python爬取抖音app视频
- 搞懂Android应用启动过程,再也不怕面试官了
- Java内存泄漏、性能优化、宕机死锁的N种姿势
- 国外服务器速度太慢 四合一加速脚本/VPS性能代码详细教程