🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 线上OOM监控方案 ## LeakCanary缺点 `LeakCanary`在线下监测内存泄漏,但是`LeakCanary`只能在线下使用,有以下问题 线下场景能跑到的场景有限,很难把所有用户场景穷尽.碰到线上问题难以定位 2.检测过程需要主动触发`GC`,`Dump`内存镜像造成`app`冻结,造成测试过程中体验不好 3.适用范围有限,只能定位`Activity`&`Fragment`泄漏,无法定位大对象、频繁分配等问题 4.`hprof`文件过大,如果整体上传的话需要耗费很多资源 上面我们介绍了`LeakCanary`不能用于线上监控的原因,所以要实现线上监控功能,就需要解决以下问题 * 监控 * 主动触发`GC`,会造成卡顿 * 采集 * `Dump hprof`,会造成`app`冻结 * `Hprof`文件过大 * 解析 * 解析耗时过长 * 解析本身有`OOM`风险 其核心流程为三部分: 1.监控`OOM`,发生问题时触发内存镜像的采集,以便进一步分析问题 2.采集内存镜像,学名堆转储,将内存数据拷贝到文件中,以下简称`dump hprof` 3.解析镜像文件,对泄漏、超大对象等我们关注的对象进行可达性分析,解析出其到`GC root`的引用链以解决问题 ![](https://img.kancloud.cn/84/7e/847ee86582955288f99815011572a7e9_2100x898.png) ## KOOM解决`GC`卡顿 `LeakCanary`通过多次`GC`的方式来判断对象是否被回收,所以会造成性能损耗 `koom`通过无性能损耗的内存阈值监控来触发镜像采集,具体策略如下: * `Java`堆内存/线程数/文件描述符数突破阈值触发采集 * `Java`堆上涨速度突破阈值触发采集 * 发生`OOM`时如果策略1、2未命中 触发采集 * 泄漏判定延迟至解析时 我们并不需要在运行时判定对象是否泄漏,以`Activity`为例,我们并不需要在运行时判定其是否泄漏,`Activity`有一个成员变`mDestroyed`,在`onDestory`时会被置为`true`,只要解析时发现有可达且`mDestroyed`为`true`的`Activity`,即可判定为泄漏 通过将泄漏判断延迟至解析时,即可解决`GC`卡顿的问题 ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dd2f54d13770470289b9518957639f69~tplv-k3u1fbpfcp-watermark.awebp) ## `KOOM`解决`Dump hprof`冻结`app` `Dump hprof`即采集内存镜像需要暂停虚拟机,以确保在内存数据拷贝到磁盘的过程中,引用关系不会发生变化,暂停时间通常长达10秒以上,对用户来讲是难以接受的,这也是`LeakCanary`官方不推荐线上使用的重要原因之一。 利用`Copy-on-write`机制,`fork`子进程`dump`内存镜像,可以完美解决这一问题,`fork`成功以后,父进程立刻恢复虚拟机运行,子进程`dump`内存镜像期间不会受到父进程数据变动的影响。 流程如下图所示: ![](https://img.kancloud.cn/0e/b3/0eb394b1cb94eb35eab4961865791a04_741x620.png) `KOOM`随机采集线上真实用户的内存镜像,普通`dump`和`fork`子进程`dump`阻塞用户使用的耗时如下: ![](https://github.com/KwaiAppTeam/KOOM/wiki/images/android_benchmark_cn.png) 可以看出,基本可以做到无感知的采集内存镜像 ## `KOOM`解决`hprof`文件过大 `Hprof`文件通常比较大,分析`OOM`时遇到500M以上的`hprof`文件并不稀奇,文件的大小,与`dump`成功率、`dump`速度、上传成功率负相关,且大文件额外浪费用户大量的磁盘空间和流量。 因此需要对`hprof`进行裁剪,只保留分析`OOM`必须的数据,另外,裁剪还有数据脱敏的好处,只上传内存中类与对象的组织结构,并不上传真实的业务数据(诸如字符串、`byte`数组等含有具体数据的内容),保护用户隐私。 裁剪`hprof`文件涉及到对`hprof`文件格式的了解,这里就不缀述了 下面看一下裁剪过程的流程图: ![](https://img.kancloud.cn/67/93/679313f1263cd8128e179951139e828b_318x607.png) ## `KOOM`解决`hprof`解析的耗时与`OOM` 解析`hprof`文件,对关键对象进行可达性分析,得到引用链,是解决`OOM`最核心的一步,之前的监控和`dump`都是为解析做铺垫。 解析分两种,一种是上传`hprof`文件由`server`解析,另一种是在客户端解析后上传报告(通常只有几`KB`)。 `KOOM`选择了端上解析,这样做有两个好处: * 1.节省用户流量 * 2.利用用户闲时算力,降低`server`压力,这样也符合分布式计算理念。 这样就可以把解析过程拆解成以下两个问题 * 哪些对象需要分析,全部分析性能开销太大,很难在端上完成,并且问题没有重点也不利于解决。 * 性能优化,作为一个`debug`组件,要在不影响用户体验的情况下完成解析,对性能有非常高的要求。 ### 关键对象判定 `KOOM`只解析关键的对象,关键对象分为两类,一类是根据规则可以判断出对象已经泄露,且持有大量资源的,另外一类是对象`shallow / retained size` 超过阈值 `Activity/fragment`泄露判定即为第一种: 对于强可达的`activity`对象,其`mDestroyed`值为`true`时(`onDestroy`时赋值),判定已经泄露。 类似的,对于`fragment`,当`mCalled`值为`true`且`mFragmentManager`为`null`时,判定已经泄露 。 `Bitmap/window/array/sufacetexture`判定为第二种 检查`bitmap/texture`的数量、宽高、`window`数量、`array`长度等等是否超过阈值,再结合`hprof`中的相关业务信息,比如屏幕大小,`view`大小等进行判定。 ### 性能优化 `KOOM`在`LeakCanary`解析引擎`shark`的基础上做了一些优化,将解析时间在`shark`的基础上优化了2倍以上,内存峰值控制在100M以内。 用一张图总结解析的流程: ![](https://img.kancloud.cn/27/15/2715df22034006d24aa7ec025785d3e0_382x667.png) 详细流程就不在这里缀述了,详情可见:[KOOM解析性能优化](https://juejin.cn/post/6860014199973871624#heading-13 "https://juejin.cn/post/6860014199973871624#heading-13") ### `KOOM`使用 `KOOM`目前已经开源,开源地址:[github.com/KwaiAppTeam…](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FKwaiAppTeam%2FKOOM "https://github.com/KwaiAppTeam/KOOM") 直接参照接入指南接入即可,当发现内存超过阈值或者发生`OOM`时,就会触发采集内存快照,对`hprof`文件进行裁剪并分析后得到报告 `KOOM`的报告是`json`格式,并且大小在`KB`级别,样式如下所示: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/53fe3a9e9ede43ff8c94458e14712623~tplv-k3u1fbpfcp-watermark.awebp) 大概包括以下信息 1.一些可能泄漏的类信息 2.泄漏原因,`gcRoot`,泄漏实例数量等 3.泄漏对象的引用链,方便定位问题 可见`KOOM`上传的数据量并不太大,但相对准确,非常便于我们分析线上数据 # 参考资料 [【从入门到实用】android内存优化深入解析](https://juejin.cn/post/6975876569990447134)