摘要
Tailor [1]是西瓜视频 Android 团队开发的一款内存快照裁剪压缩工具,广泛用于字节跳动旗下各大 App 的 OOM 治理及异常排查,收益显著,在西瓜视频上更是取得 OOM 降低95%以上的好成绩。Tailor 工具现已开源,本文将通过原理、方案和实践来剖析 Tailor 的相关细节。
背景
稳定性治理一直是个老生常谈的话题,过去我们调查稳定性问题只能依靠堆栈和源码,但很多时候堆栈是远远不够的,对于严重依赖的数据只能临时增加埋点后再次上线搜集,这期间还会遇到能不能搜集到和怎么搜集的问题,使得我们治理稳定性问题时常常过于局限和被动。探寻通用、高效、便捷的异常数据搜集方案一直是我们在治理实践中努力的方向。
西瓜视频 Android 团队基于Java 堆内存快照,搭建了一套相对完整的通用异常数据搜集系统,能够在异常发生时,尝试 dump 出一个相对完整的内存快照文件,必要的时候借助云控系统实现快照回捞,最终通过内存快照辅助调查那些棘手的稳定性问题,以提升稳定性问题的治理效率。如何高效、安全、便捷的获取内存快照,是整个通用异常数据搜集系统里关键的一环。
内存快照的作用
OOM 治理
我们知道内存快照是治理 OOM 问题及其他类型的内存问题的重要数据源,其重要性可以简单理解为:内存快照是解决常规堆内存 OOM 问题的充分条件。同时,内存快照中保存的对象信息和依赖关系也是静态分析内存泄漏的关键,是所有内存泄漏检测工具的基石。
Crash 治理
内存快照中保存的数据,很多时候也是调查其他类型异常的重要参考,比如 Activity、Fragment、View 状态等、Framework 层及第三方对象的数据等,必要的时候都可以用来分析异常问题。作为通用数据大大减少了定向埋点的烦恼,同时也覆盖了很多无法渗透到的场景。
为什么要做裁剪
为了能在需要的时候为各类异常提供数据支持,必须要保证数据的稳定,这就需要解决快照在 dump、存储、传输等环节可能存在的问题,不仅包括存储空间和流量消耗问题,还包括隐私和安全性问题。
存储
以 LargeHeap 应用为例,其 OOM 时的内存快照大小通常在512M左右。不经过裁剪的话只能存储在App的外部存储空间或者 SDcard 上,这就会遇到空间不足或者 SDcard 的权限问题( Android 11对 App 的外部存储空间也做了权限限制)。没有足够稳定的存储空间,快照dump成功率将会大幅降低。
传输
传输过程对于数据的大小是非常敏感的,首当其冲的就是流量消耗问题,其次更小的快照传输耗时更少,回传的成功率也会大幅提升。
隐私
内存快照是虚拟机堆内存数据的完整 copy,这其中可能包含有账号、Token、联系人、密钥以及其他可能存在隐私的图片/字符串等,隐私数据是必须要裁剪掉的。
内存快照裁剪方案
目前已知的裁剪方案有种:一种是已开源的 Matrix 方案,另一种是本人在 2018 提出的 hprof 流裁剪方案。Matrix 方案分为两步:先通过 Debug.dumpHprofData 直接 dump 出一个完整的 hprof 文件;然后通过分析 hprof 文件分别裁剪掉数据相同的 Bitmap 对象和 String 对象。其裁剪方案存在以下问题:
原生接口直接 dump 出的 hprof 文件过大,存储问题不好解决;
裁剪过程中涉及到大文件 I/O 和 hprof 分析,对 App 性能的影响不好控制;
裁剪不彻底,快照中仍然存在大量无用数据和可能存在的隐私问题。
hprof 流裁剪则是基于 hprof 文件格式,在 hprof 文件写入过程中进行裁剪压缩,存储空间问题大幅改善,也没有大文件 I/O 和 hprof 分析过程带来的性能问题。该方案源于实际的 OOM 治理需求,并参考了hprof 文件的格式定义,相关考虑如下:
治理需要
对于 OOM 问题,只有对象大小和引用关系是必须的,其余信息都是次要的;
OOM 时占比最大的对象通常是 Bitmap/String,这些对象的数据主要消耗在 byte[] 、 char[];
Java 堆中的明文隐私信息通常以 Bitmap/String 的形式存在。
hprof格式
hprof [2]文件有明确定义,其数据组织形式比较简单,整体可分为 Header和 Record 数组两部分,相关数据组织定义如下:
Header: "JAVA PROFILE 1.0.2" + indetifiers + timestamp (13byte + 4byte + 8byte)