西瓜视频稳定性治理体系建设一:Tailor 原理及实践
liuian 2025-04-30 18:01 49 浏览
摘要
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)
- Record:tag + time + length + body(1byte + 4byte + 4byte + byte[$length])
Android 上 dump 出的 hprof 文件虽然也遵循 hprof 格式,但也有所不同,典型的是其一级TAG只有:STRING、LOAD_CLASS、HPROF_TAG_STACK_TRACE、HEAP_DUMP_SEGMENT、HEAP_DUMP_END。HEAP_DUMP_SEGMENT 又分了很多二级 TAG ,这些二级 TAG 中既有标准 hprof 定义的,也有 Android 自定义的 TAG。跟裁剪关系比较紧密的二级 TAG 是 PRIMITIVE_ARRAY_DUMP,存放的是诸如 byte[] 、char[] 、int[]等类型的数据,其格式如图所示:
通过 hprof 格式定义可以发现,直接裁剪掉所有的 byte[]和 char[]就可以实现对 Bitmap/String 对象的裁剪。同时其数据格式定义中还存在大量的无用数据,比如 timestamp、class-serial-number、stack-serial-number、reserved 数据等等,4byte 的 length/number 等也可以压缩成 3byte 或者 2byte 等等。
Tailor 裁剪压缩实现
如果只为了治理 OOM,可以进行最大化裁剪(如byte[]、char[]、boolean[]、short[]、float[]、int[]、double[]、long[]、hprof格式裁剪),甚至可以只保留 app-heap。但作为通用异常数据,西瓜视频也会在必要的时候,通过回捞快照来分析非 OOM 类的异常,甚至是 native 异常。随着稳定性治理的深入,快照更多是用来分析非 OOM 异常。对于非 OOM 异常,快照的完整性尤为重要,同时非 OOM 的 crash 堆内存通常较小,最大化裁剪没有必要,综合考虑之后 Tailor 只保留了 byte[]、char[] 和 hprof 格式裁剪。
快照 dump 的过程大致可以分为 5 步,Tailor 只关注 open 和 write 环节。通过 xHook [3](针对 Android 平台 ELF 的 PLT hook库)在 native 层 hook dump 过程必然会调用到的 open/write 函数,以此实现对hprof 文件写入流的代理,进而进行 hprof 流裁剪。为了进一步降低写入后的文件体积,Tailor 会在裁剪之后直接进行 zlib 流压缩。流程大致如下:
- 调用 Tailor.dumpHprofData() 时,会依次调用 nOpen()、Debug.dumpHprofData()、nClose();
- nOpen 在 native 层开启对 int open(const char* __path, int __flags, ...)和 ssize_t write_proxy(int fd, const char*buffer, size_t count) 的 hook 代理;
- Debug.dumpHprofData 执行中会先调到 open 函数,hook 代理逻辑会过滤出目标文件的 fd;调到 write 函数时 hook 代理逻辑通过 fd 过滤出目标文件的写入数据进行裁剪压缩;
- nClose 逻辑清除之前的 hook 代理。
// isGzip 是否在裁剪之后进行zip压缩
public static synchronized void dumpHprofData(String fileName, boolean isGzip) throws IOException {
nOpen(fileName, isGzip);
Debug.dumpHprofData(fileName);
nClose();
}
Tailor 裁剪压缩效果
实际的裁剪效果取决于具体现场,OOM 现场的快照通常比较大(LargeHeap/非 LargeHeap 的差异也很大),非 OOM 的则要小很多,根据西瓜视频(LargeHeap)的实践经验可以得出以下数据:
- 体积
- OOM:约 50%可以裁剪压缩到 10M 以内。
- 非 OOM:约 60%可以裁剪压缩到 5M 以内,约 90%可以裁剪压缩到 10M 以内。
- 耗时
- 同原生 dump 耗时相差很小:dump 过程的耗时主要集中在两次 ProcessHeap 调用和文件写入上。
- 稳定性
- 基本没有稳定性问题:此开源版本已运行半年以上,未发现有 Tailor 相关的 crash。
西瓜视频治理实践
西瓜视频汇集了短、中、长各类视频资源,人均使用时长超过 100 分钟,同时启动次数又相对较少,导致内存问题会被放大,进而导致治理难度加大。以西瓜视频 Android v4.0.0 为例,这期间 Java crash 约为 6.5 左右(影响用户的 DAU 占比),而其中 OOM 就高达 3.4,占比过半 。
OOM 问题常见的治理思路,基本都是通过内存泄漏检测工具实现的,这类工具的局限性在于其输出的是孤立的内存泄漏 case,缺少对整体堆内存影响的评估,无法从泄漏中看出 OOM 的直接原因,还存在比较严重的误报行为。虽然后续很多新的工具在性能上有所提升,但本质仍属于 LeakCanary 这一体系,并未从根本上解决工具在治理 OOM 时所面临的问题。
针对这种情况,西瓜视频 Android 完全抛弃了线上内存泄漏检测机制,开发完善了 Tailor 内存快照裁剪压缩工具,并以此为核心制定了线上线下同步治理的长效策略:
- 线下开发、回归、Monkey、压测等环节自动集成 LeakCanary 检测内存泄漏;
- 线上 OOM 时通过 Tailor 主动 dump 内存快照,通过回捞快照精准分析 OOM 问题。
该策略将治理防范的重点放到了线下,在建设完善内存问题前置发现能力的同时,也避免线上分析带来的性能影响和问题规模爆炸。同时,通过 Tailor 内存快照裁剪压缩工具和回捞机制,使得整个内存优化治理形成闭环,以线下防范为主,线上精准治理为辅,线上反哺线下,既可以精准高效地治理线上所有的堆内存 OOM 问题,又补充完善了线下监控体系。
经过一段时间的治理,西瓜视频 Android 新版本的 Java 堆内存 OOM 问题从 3.5 降低到了 0.03,直接降低了两个数量级,并能长期以极低的人力投入保持下去。与此同时,我们也通过内存快照解决了大量迭代过程中遇到的其他类型的棘手的异常,不仅拓展了稳定性治理的思路,也沉淀出了新的稳定性治理的方法论。
在实际治理过程中,很多时候对于堆栈无法直接定位的问题,我们只能通过分析业务代码、分析增量代码、AB 实验等方法来定位。当第二次遇到时,即便知道原因,仍然需要重复之前繁琐的调查,治理经验太过主观,很难传承。而通过内存快照则不会有此类问题,快照的分析过程是客观可重复的,每解决一类问题,后续再遇到是完全可以复制之前的分析过程的。
堆内存 OOM 治理
事实上由于泄漏直接导致的 OOM 问题相对较少,能直接导致 OOM 或者内存水位比较高的,更多的是业务逻辑、缓存逻辑等,这些很多是现有检测工具覆盖不到的。事实上对于大多数 App 而言,实际能够导致 OOM 的原因十分有限,通过快照可以很直观的发现问题。
上图所示的是一个 OOM 现场,通过内存泄漏检测工具,的确可以找出多处泄漏,但都不是导致 OOM 的根本原因。即便修复了这些泄漏,显然也无法解决此类 OOM 问题(Android 硬件加速逻辑的漏洞,导致大量 byte[] 被 JNI Global 持有而泄漏)。
其他Crash治理
内存快照也是及其重要的数据现场,对于调查数据状态相关稳定性问题,是极为重要的数据补充。如果我们在非 OOM 类的 crash 时,也能获取内存快照,那么就获取了crash 时完整的内存状态。对于堆栈无法定位的问题,可以结合源码和快照数据来辅助调查问题,以下是三个典型的案例:
案例1
上图是一个比较常见的 Java crash 堆栈,堆栈中没有业务相关的信息,对于业务比较复杂的 App,传统手段很难快速定位。通过快照来调查此问题,就变得非常简单了:MAT 里先筛选出 mRecycled 为 true 的 Bitmap 对象,再通过「Path to GC Roots」即可定位。
案例2
上图同样是没有任何业务信息的 crash 堆栈,通过源码判断是在
mListener.onSurfaceTextureAvailable 回调里间接将 mLayer 置空导致的。由于置空代码位于 Framework 层,无法通过打点拿到相关 trace。
最后我们通过快照过滤出 crash时的 TextureLayer 实例,发现其 mAttachInfo 为 null,断定是在回调里执行 removeView 而最终导致 mLayer 被置空的,再通过这个 TextureLayer 实例逐层向上找到 mParent 为 null 的节点,最终找定位到被 remove 的上层节点,进而定位到了问题场景。
案例3
西瓜视频里经常遇到 OutOfMemoryError: pthread_create (1040KB stack) failed 类型的 native OOM,有一类明确因为播放器实例过多,导致 native 层缓存占用过大而 OOM 的。究竟是播放器自身的问题,还是业务层的问题很难判断。如果通过针对性的埋点来搜集数据太被动,而通过快照里 Java 层 player 对象的状态、引用关系来判断则非常简单,此类问题前后有三类:业务层未正确释放 player、player 的异步 release 被 block、standard 的 Activity 过多导致 player 实例过多等。
根据西瓜视频团队的实践,大量无法通过堆栈来定位的问题,通过快照则可以很轻松的定位到原因。那些即便不能直接定位到问题原因的 case,内存快照也可以提供必要的数据支持。以下是西瓜视频团队实践中总结出的典型的可以通过内存快照来辅助调查的问题分类:
- Framework:完整的 Activity、Fragment、View 状态,完整的 Framework 层数据&状态。
- 插件类问题:有完整的插件&状态信息、Class、Classloader 及 dex 信息等等。
- 业务层问题
- 第三方问题
内存快照裁剪后续
作为一个立足于提升稳定性治理效率的基础工具,能在必要的时候为任何可能的异常提供完整通用的数据现场,是其当仁不让的职责。能否提供完整的数据现场,核心集中在 dump、存储、传输三个环节,因而 dump 速度、体积、完整性也就成为了核心优化方向。基于目前的成果,对比 Android 原生的快照 dump 逻辑,内存快照裁剪压缩工具在以下方面还有进一步的优化提升空间:
裁剪压缩比
在保证快照数据尽可能完整的前提下,怎样进一步裁剪体积是个矛盾的问题,基于 hprof 格式裁剪仍有很大空间。同时,也可以探索其他高效的裁剪方案,以裁剪掉最终分析时用不到的数据。
裁剪压缩速度
目前 Tailor 的裁剪压缩耗时跟原生 dump 耗时比较接近,这是因为裁剪压缩的过程耗时有限,主要时间消耗在两次调用 ProcessHeap 和文件写入上,直接干掉第一次调用将会大幅减少整个 dump 耗时。
Dump内存消耗
Android 快照 dump 是在 native 层完成的,dump 过程中每个 Record 都是通过 std::vector<uint8_t> 先缓存之后,再写入到文件里的,实际 dump 过程中 Record 可能会非常大,这时就需要额外申请内存。而当我们是在 native 内存不足的 crash 现场,dump Java 堆内存快照时会大概率失败(大多数 native 内存不足都是由于 Java 层的业务逻辑导致的,必要的时候可以通过 Java 堆现场来定位问题)。如何保证在 native 内存不足时,也能成功 dump 内存快照,是值得思考的。
通过分析相关源码可以发现,实际只需要 hook 下列接口,就可以代理 Record 的缓存过程,直接对拦截到的数据进行裁剪压缩,就不会有 Record 缓存空间的问题,也可以提升快照 dump 的速度。
总结
Android 稳定性治理发展至今,相关的监控工具和方法论并不完善。基于内存快照的治理思路和分析方法,将会是传统稳定性治理体系的重要补充,其分析过程更客观、直接、高效,有效减少数据埋点的同时也净化了代码逻辑,将内存快照作为通用异常数据进行搜集可以一劳永逸。
内存快照裁剪压缩是通用异常数据搜集系统里至关重要的一环,是关系到整个技术路线是否通用的核心和关键。Tailor 只是迈开了其中的一小步,方案还有很大的优化空间。开源不是终点,我们希望集思广益、共同探索完善,在 Android 稳定性治理上走的更快更远。
接下来我们会逐步开源并详细介绍西瓜 Android 性能稳定性团队的其他核心监控体系建设,这其中主要有:Raphael(Native 内存泄漏监控工具)和 Sliver(高性能 Trace 工具)等,覆盖 Native 内存泄漏检测、ANR 治理、卡顿治理、基础性能优化等方向,敬请关注!
相关资料
- Tailor 开源地址:https://github.com/bytedance/tailor
- HPROF 协议:http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share/demo/jvmti/hprof/manual.html#mozTocId848088
- xHook 链接:https://github.com/iqiyi/xHook
- Android Camera内存问题剖析 (基于 Tailor 和内存快照的实战案例)
更多分享
欢迎关注「 字节跳动技术团队 」
简历投递联系邮箱「 tech@bytedance.com 」
相关推荐
- 路由器组网具体连接方法(路由器组网教程)
-
举例:你正在用一个TP-LINK的无线路由器上网,信号不是太好,你想在下边再连一个无线路由器的话,就得在第二个路由器上设置了,先不管第一个路由器,设置步骤:把第二个无线路由器连接到电脑上(只是路...
- 雨林木风win7纯净版gho(雨林木风win7官网)
-
雨林木风WIN7光盘重装系统的步骤是将光盘放入光驱内,设置光驱为第一启动盘,打开电脑后进入光盘引导,最后将系统文件镜像到系统盘上1.你下载的雨林木风GHOSTXPSP3纯净版Y8.0是一个克隆光...
- 电脑一直正在重新启动怎么解决
-
1、电脑误删除或者是破坏了系统文件。那么需要重新安装系统。2、可能是安装的软件或者是插件跟系统兼容性冲突导致的。可以强制关机3次,然后重新启动电脑会出现恢复界面——选择安全模式——然后电脑会重新启...
-
- 如何下载ps软件免费版(ps如何下载免费版本)
-
1.在搜索引擎中输入adobe并点击搜索。2.点击adobe官网。3.点击支持并点击下载与安装。4.点击开始免费试用并点击下载。5.打开文件夹并点击打开进行安装即可。6.根据以上步骤即可下载安装ps。AdobePhotoshopCS6号...
-
2026-01-14 05:37 liuian
- 怎么判断该换硅脂了(cpu硅脂干了影响真的很大吗)
-
方法步骤如下第一,从系统软件的运行上来看,如果在运行某些大型软件,容易导致显卡发热的程序时,出现画面掉帧,或卡顿,甚至是画面卡死等情况,这大多是因为显卡散热出现问题,导致显卡自动降频,以降低功耗来减少...
- 壁纸图片2025最新款(电脑桌面壁纸图片2025最新款)
-
要更换2023最新款壁纸图片,可以按照以下步骤操作:首先,找到您想要更换的壁纸图片并下载到您的设备上。其次,进入您的设备设置,找到“壁纸”或“桌面壁纸”选项,并点击进入。然后,选择“更换壁纸”并在相册...
- 清理垃圾的神器(清理垃圾的神器是什么)
-
1、《腾讯手机管家》这款可以帮助用户进行强力的清理,加速告别空间卡顿,缓慢延迟的问题的软件当中,用户可以随时随地登录软件进行自动清理和自动清理,自动清理包括图片,视频,语音文件在内的各种换成文件,为手...
- 苹果笔记本怎样重装系统(苹果笔记本怎样重装系统还原)
-
苹果笔记本电脑系统可以通过以下步骤进行重装:1.备份数据:在开始重装前,需要备份你的重要数据。你可以将数据存储到外部硬盘、云存储或其他可靠的设备中。2.下载安装器:从AppStore中下载macOS...
- 手机wifi打不开怎么办
-
手机wifi打不开的原因,可能集中在该手机出现了手机文件丢失、手机版本不稳定、手机文件出错以及手机wifi模块摔坏等故障造成的。手机wifi打不开修复教程1.wcnss_qcom_cfg文件丢失导...
- bios恢复出厂设置后无法开机
-
可通过进入BIOS界面设置bios恢复出厂设置的方法解决,步骤如下:1、通过按Delete或数字键盘中的Del键进入BIOS。2、按箭头键输入并将光标移动到“加载设置默认值”项,然后按enter确认。...
- 电脑硬盘打不开怎么办(电脑硬盘打不开怎么办)
-
电脑硬盘坏了是不能开机的。硬盘坏道的修复方法:1、逻辑坏道的修复对于逻辑坏道,Windows自带的“磁盘扫描程序(Scandisk)”就是最简便常用的解决手段。如果硬盘出现了坏道,我们可在Window...
- linux系统备份与还原工具(linux系统备份与还原工具在哪)
-
用GHOST对LINUX系统做备份1:要求将安装了LINUX系统的硬盘(原盘)整盘刻至另一硬盘(目标盘)。2:所需工具:DOS系统引导盘,GHOST2003(版本低的对文件格式不能很好的支持),原盘(...
- pdf怎么转换成xml格式(如何将pdf格式转换成xml格式)
-
将PDF转换为XML需要使用专业的PDF转换工具。以下是一些常用的PDF转XML工具:1.AdobeAcrobatDC:AdobeAcrobatDC是一款功能强大的PDF编辑软件,其中包括P...
- windows7iso文件(iso文件 win7)
-
利用winrar可以直接打开iso文件,如果双击不能直接打开需要设置winrar,步骤如下:1、启动winrar,点击选项菜单设置命令;2、点击综合选项卡,点击全部选择,点击确定即可。具体操作方法步骤...
- 路由器ip地址是什么意思(路由器的ip地址是)
-
路由器IP地址是指连接到互联网的路由器在局域网内的唯一标识符,一般为192.168.1.1或192.168.0.1等地址。通过路由器IP地址,用户可以通过浏览器等工具登录到路由器管理界面,进行网络设置...
- 一周热门
-
-
飞牛OS入门安装遇到问题,如何解决?
-
如何在 iPhone 和 Android 上恢复已删除的抖音消息
-
Boost高性能并发无锁队列指南:boost::lockfree::queue
-
大模型手册: 保姆级用CherryStudio知识库
-
用什么工具在Win中查看8G大的log文件?
-
如何在 Windows 10 或 11 上通过命令行安装 Node.js 和 NPM
-
威联通NAS安装阿里云盘WebDAV服务并添加到Infuse
-
Trae IDE 如何与 GitHub 无缝对接?
-
idea插件之maven search(工欲善其事,必先利其器)
-
如何修改图片拍摄日期?快速修改图片拍摄日期的6种方法
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)
