Liko 流程图
图 4. Liko 流程图
整体流程分为:
-
Hprof 收集
-
分析时机
-
分析策略
Hprof 收集
收集过程我们设置了多种策略可以自由组合,主要有 OOM、内存触顶、内存激增、监测 Activity、Fragment 泄漏数量达到一定阈值时触发,线下线上策略配置不同。
为了解决 dump 挂起进程问题,我们采用了子进程 dump+fileObsever 的方式完成 dump 采集和监听。
在 fork 子进程之前先 Suspend
获取主进程中的线程拷贝,通过 fork 系统调用创建子进程让子进程拥有父进程的拷贝,然后 fork 出的子进程中调用 Hprof 的 DumpHeap
函数即可完成把耗时的 dump 操作在放在子进程。由于 suspend
和 resume
是系统函数,我们这里通过自研的 native hook 工具对 libart.so
hook 获取系统调用。由于写入是在子进程完成的,我们通过 Android 提供的 fileObsever 文件写入进行监控获取 dump 完成时机。
图 5.子进程 dump 流程图
Hprof 分析时机
为了达到分析过程对于用户无感,我们在线上、线下配置了不同的分析时机策略,线下在 dump 分析完成后根据内存状态主动触发分析,线上当用户下次冷启退出应用后台且内存充足的情况下触发分析。
分析策略
分析策略我们提供了两种,一种在 Android 客户端分析,一种回传至 Server 端分析,均通过 MAT 分析引擎进行分析。
端上分析
分析引擎
端上分析引擎的性能很重要,这里我们主要对比了 LeakCanary 的分析引擎 Shark 和 Haha 库的 MAT。
图 6. Shark VS MAT
我们在相同客户端环境对 160M 的 HPROF 多次分析对比发现 MAT 分析速度明显优于 Shark,另外针对 MAT 分析后仍持有统治者树占用内存我们也做了主动释放,对比性能收益后采用基于 MAT 库的分析引擎进行分析,对内存泄漏引用链路自动归并、大对象小对象引用链自动分析、大图线下自动还原线上过滤无用链路,分析结果如下:
内存泄漏
图 7. 内存泄漏链路
对泄漏的 Activity 的引用链进行了聚合分析,方便一次性解决该 Activity 的泄漏链释放内存。
大对象
图 8. 大对象链路
大对象不止分析了引用链路,还递归分析了内部 top 持有对象(InRefrenrece
)的 RetainedSize。
小对象
图 9. 小对象链路
小对象我们对 top 的外部持有对象(OutRefrenrece
)进行聚合得到占有小对象最多的链路。
图片
图 10. 图片链路
图片我们过滤了图片库等无效引用且对 Android 8.0 以下的大图在线下进行了还原。
回传分析
为了最大限度的节省用户流量且规避隐私风险,我们通过自研 HPROF 裁剪工具 Tailor 在 dump 过程对 HPROF 进行了裁剪。