[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)
- Android
- 四大组件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介绍
- MessageQueue详细
- 启动流程
- 系统启动流程
- 应用启动流程
- Activity启动流程
- View
- view绘制
- view事件传递
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大数据
- Binder小结
- Android组件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 迁移与修复
- Sqlite内核
- Sqlite优化v2
- sqlite索引
- sqlite之wal
- sqlite之锁机制
- 网络
- 基础
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP进化图
- HTTP小结
- 实践
- 网络优化
- Json
- ProtoBuffer
- 断点续传
- 性能
- 卡顿
- 卡顿监控
- ANR
- ANR监控
- 内存
- 内存问题与优化
- 图片内存优化
- 线下内存监控
- 线上内存监控
- 启动优化
- 死锁监控
- 崩溃监控
- 包体积优化
- UI渲染优化
- UI常规优化
- I/O监控
- 电量监控
- 第三方框架
- 网络框架
- Volley
- Okhttp
- 网络框架n问
- OkHttp原理N问
- 设计模式
- EventBus
- Rxjava
- 图片
- ImageWoker
- Gilde的优化
- APT
- 依赖注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 协程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 运行期Java-hook技术
- 编译期hook
- ASM
- Transform增量编译
- 运行期Native-hook技术
- 热修复
- 插件化
- AAB
- Shadow
- 虚拟机
- 其他
- UI自动化
- JavaParser
- Android Line
- 编译
- 疑难杂症
- Android11滑动异常
- 方案
- 工业化
- 模块化
- 隐私合规
- 动态化
- 项目管理
- 业务启动优化
- 业务架构设计
- 性能优化case
- 性能优化-排查思路
- 性能优化-现有方案
- 登录
- 搜索
- C++
- NDK入门
- 跨平台
- H5
- Flutter
- Flutter 性能优化
- 数据跨平台