百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

西瓜视频稳定性治理体系建设一:Tailor 原理及实践

cac55 2025-03-04 10:57 44 浏览 0 评论

摘要

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,内存快照也可以提供必要的数据支持。以下是西瓜视频团队实践中总结出的典型的可以通过内存快照来辅助调查的问题分类:

  1. Framework:完整的 Activity、Fragment、View 状态,完整的 Framework 层数据&状态。
  2. 插件类问题:有完整的插件&状态信息、Class、Classloader 及 dex 信息等等。
  3. 业务层问题
  4. 第三方问题

内存快照裁剪后续

作为一个立足于提升稳定性治理效率的基础工具,能在必要的时候为任何可能的异常提供完整通用的数据现场,是其当仁不让的职责。能否提供完整的数据现场,核心集中在 dump、存储、传输三个环节,因而 dump 速度、体积、完整性也就成为了核心优化方向。基于目前的成果,对比 Android 原生的快照 dump 逻辑,内存快照裁剪压缩工具在以下方面还有进一步的优化提升空间:

裁剪压缩比

在保证快照数据尽可能完整的前提下,怎样进一步裁剪体积是个矛盾的问题,基于 hprof 格式裁剪仍有很大空间。同时,也可以探索其他高效的裁剪方案,以裁剪掉最终分析时用不到的数据。

裁剪压缩速度

目前 Tailor 的裁剪压缩耗时跟原生 dump 耗时比较接近,这是因为裁剪压缩的过程耗时有限,主要时间消耗在两次调用 ProcessHeap 和文件写入上,直接干掉第一次调用将会大幅减少整个 dump 耗时。

Dump内存消耗

Android 快照 dump 是在 native 层完成的,dump 过程中每个 Record 都是通过 std::vector 先缓存之后,再写入到文件里的,实际 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 治理、卡顿治理、基础性能优化等方向,敬请关注!

相关资料

  1. Tailor 开源地址:https://github.com/bytedance/tailor
  2. HPROF 协议:http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share/demo/jvmti/hprof/manual.html#mozTocId848088
  3. xHook 链接:https://github.com/iqiyi/xHook
  4. Android Camera内存问题剖析 (基于 Tailor 和内存快照的实战案例)

更多分享

基于有限状态机与消息队列的三方支付系统补单实践

UME - 丰富的Flutter调试工具

一例 Go 编译器代码优化 bug 定位和修复解析


欢迎关注「 字节跳动技术团队 」

简历投递联系邮箱「 tech@bytedance.com

相关推荐

MIRIX重塑AI记忆:超Gemini 410%,节省99.9%内存,APP同步上线

MIRIX,一个由UCSD和NYU团队主导的新系统,正在重新定义AI的记忆格局。在过去的十年里,我们见证了大型语言模型席卷全球,从写作助手到代码生成器,无所不能。然而,即使最强大的模型依...

硬盘坏了怎么把数据弄出来对比10种硬盘数据恢复软件

机械硬盘或固态硬盘损坏导致数据丢失时,应立即停止对硬盘的读写操作,并根据损坏类型选择逻辑层恢复工具或专业物理恢复服务。紧急处置措施立即停止通电使用:发现硬盘异响、无法识别或数据异常时,需立即断开连接,...

蓝宝石B850A WIFI主板新玩法:内存小参调节体验

蓝宝石前段时间发布了一款性价比极高的主板:NITRO氮动B850AWIFI主板。这款主板的售价只要1349元,相比普遍1500元以上的B850主板,确实极具竞争力。虽然价格实惠,蓝宝石NITR...

内存卡损坏读不出怎么修复?这5个数据恢复工具汇总,3秒挽回!

在数字化生活的浪潮中,内存卡凭借小巧便携与大容量存储的特性,成为相机、手机、行车记录仪等设备存储数据的得力助手,承载着无数珍贵回忆与重要文件。然而,当内存卡突然损坏无法读取,无论是误删、格式化、病毒入...

内存卡修复不再难,2025年必学的6款软件工具

内存卡出现问题时,通常是因为文件系统损坏、物理损坏或病毒感染。通过专业的修复工具,我们可以尝试恢复数据并修复内存卡。内存卡修复利器:万兴恢复专家万兴恢复专家是一款功能强大的数据恢复软件,支持多种设备和...

有5款内存卡修复工具汇总,内存卡数据轻松找回!

在如今的数字时代,内存卡作为不可或缺的存储介质,广泛应用于相机、手机、行车记录仪等各类设备中,承载着我们珍贵的照片、视频以及重要文件。然而,数据丢失的风险却如影随形,误删、格式化、病毒入侵、硬件故障等...

揭秘:如何通过多种方式精准查询内存条型号及规避风险?

以下是内存条型号查询的常用方法及注意事项,综合了物理查看、软件检测、编码解析等多种方式:一、物理标签查看法1.拆机查看标签打开电脑主机/笔记本后盖找到内存条,观察标签上的型号标识。例如内存标签通常标...

内存卡数据恢复5个工具汇总推荐,轻松找回珍贵记忆!

在这个数字化时代,内存卡作为我们存储珍贵照片、重要文件的常用载体,广泛应用于手机、相机、平板电脑等设备。但数据丢失的意外却常常不期而至,误删除、格式化、病毒攻击,甚至内存卡的物理损坏,都可能让辛苦保存...

电脑内存智能监控清理,优化性能的实用软件

软件介绍Memorycleaner是一款内存清理软件。功能很强,效果很不错。Memorycleaner会在内存用量超出80%时,自动执行“裁剪进程工作集”“清理系统缓存”以及“用全部可能的方法清理...

TechPowerUp MemTest64:内存稳定性测试利器

TechPowerUpMemTest64:内存稳定性测试利器一、软件简介TechPowerUpMemTest64,由知名硬件信息工具GPU-Z的出品公司TechPowerUp发布,是一款专为64位...

微软推出AI恶意软件检测智能体Project Ire,精确度高达98%

IT之家8月6日消息,当地时间周二,微软宣布推出可自主分析恶意软件的AI检测系统原型——ProjectIre。该项目由微软研究院、Defender研究团队及Discovery&a...

农村老木匠常用的20种老工具,手艺人靠它养活一家人,你认识几种

生活中的手艺老匠人是非常受到尊敬和崇拜的,特别是在农村曾经的老匠人都是家里的“座上宾”。对于民间传统的手艺人,有一种说法就是传统的八大匠:木匠、泥匠、篾匠、铁匠、船匠、石匠、油匠和剃头匠。木匠的祖始爷...

恶意木马新变种伪装成聊天工具诱人点击

国家计算机病毒应急处理中心通过对互联网监测发现,近期出现一种恶意木马程序变种Trojan_FakeQQ.CTU。该变种通过伪装成即时聊天工具,诱使计算机用户点击运行。该变种运行后,将其自身复制到受感染...

学习网络安全 这些工具你知道吗?

工欲善其事必先利其器,在新入门网络安全的小伙伴而言。这些工具你必须要有所了解。本文我们简单说说这些网络安全工具吧!Web安全类web类工具主要是通过各种扫描工具,发现web站点存在的各种漏洞...

5分钟盗走你的隐私照片,这个全球性漏洞到底有多可怕?

这个时代,大家对电脑出现漏洞,可能已经习以为常。但如果机哥告诉大家,这个漏洞能够在5分钟内,破解并盗取你所有加密文件,而且还无法通过软件和补丁修复...这可就有点吓人啦。事情是酱婶的。来自荷兰埃因...

取消回复欢迎 发表评论: