1.KOOM简介
2.KOOM基础使用流程
3.KOOM的dump触发时机
4.KOOM高性能的fork dump
5.线上采集性能对比
1.KOOM简介
2020年快手开源KOOM (OOM Killer),KOOM是首个开源的线上内存溢出OOM问题的解决方案。
KOOM已在快手全量业务中应用,OOM率降低了80%以上,效果显著。
客户端OOM问题是比较难处理的。一般的崩溃问题,只要获取崩溃瞬时的数据,比如异常类型、堆栈等就都比较容易解决,而 OOM 往往是多个因素累加在一起形成的,并且和用户操作的过程有很大关系。没有成熟的线上内存监控体系,一般我们只能采用线下复现的方式,效率很低下,不能满足需求。
主流的LeakCanary可以针对Activity或者Fragment的来优化OOM问题,但是受限于性能原因,线上无法大规模使用,一搬都是用于线下定位问题使用。还有其他的方案比如腾讯Matrix的ResouceCanary,也是基于LeakCanary为基础做优化,没有完全解决监控过程中的性能问题。
KOOM也是沿用行业思路,基于LeakCanary进行自研改造,补足原来的短板,打造了一套一站式服务的监控系统。
KOOM是在客户端完成内存监控后,自动或者手动将报告上传云端,文件经过优化处理大小在KB级别,运行时无感知,流量占用非常少,可以大规模应用。
2.KOOM基础使用流程
github地址:
近2000 Star
最后生成的json报告形式,比如CommonUtils持有Activity时
{
"analysisDone":true,
"classInfos":[
{
"className":"android.app.Activity",
"instanceCount":4,
"leakInstanceCount":3
},
{
"className":"android.app.Fragment",
"instanceCount":4,
"leakInstanceCount":3
},
{
"className":"android.graphics.Bitmap",
"instanceCount":115,
"leakInstanceCount":0
},
{
"className":"libcore.util.NativeAllocationRegistry",
"instanceCount":1513,
"leakInstanceCount":0
},
{
"className":"android.view.Window",
"instanceCount":4,
"leakInstanceCount":0
}
],
"gcPaths":[
{
"gcRoot":"Local variable in native code",
"instanceCount":1,
"leakReason":"Activity Leak",
"path":[
{
"declaredClass":"java.lang.Thread",
"reference":"android.os.HandlerThread.contextClassLoader",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"java.lang.ClassLoader",
"reference":"dalvik.system.PathClassLoader.runtimeInternalObjects",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"",
"reference":"java.lang.Object[]",
"referenceType":"ARRAY_ENTRY"
},
{
"declaredClass":"com.kwai.koom.demo.CommonUtils",
"reference":"com.kwai.koom.demo.CommonUtils.context",
"referenceType":"STATIC_FIELD"
},
{
"reference":"com.kwai.koom.demo.LeakActivity",
"referenceType":"instance"
}
],
"signature":"569fc01daea06b6cc679bd61725affd163d022c3"
}
],
"runningInfo":{
"analysisReason":"RIGHT_NOW",
"appVersion":"1.0",
"buildModel":"MI 9 Transparent Edition",
"currentPage":"LeakActivity",
"dumpReason":"MANUAL_TRIGGER",
"jvmMax":512,
"jvmUsed":2,
"koomVersion":1,
"manufacture":"Xiaomi",
"nowTime":"2021-09-07_16-07-34",
"pss":32,
"rss":123,
"sdkInt":29,
"threadCount":17,
"usageSeconds":40,
"vss":5674
}
}
复制代码
主要是:类信息、gc引用路径、运行基本信息
2.1接入方式
2.2初始化
2.3 Java-OOM报告获取
当内存异常时,采集内存镜像、分析之后会生成一份json文件报告
手动获取
实时监听报告生成
设置Uploader
2.4 自定义需求
配置KConfig设置需要的各项参数
默认heapRatio的设置,会根据最大内存来调整设置更合理的值
- 应用内存>512M 设置80%
- 应用内存>256M 设置85%
- 应用内存>128M 设置90%
3.KOOM的监控触发时机
LeakCanary 和 Matrix 都是在 Activity的onDestroy 时触发泄漏检测,而KOOM是使用阈值检测的方法来触发。
KOOM改用阈值检测,有什么好处?
传统的方案是onDestroy()后连续触发两次GC,并检查引用队列,判定Activity是否发生了泄漏,但频繁GC会造成用户可感知的卡顿,KOOM为实现无感触发设计了全新的监控模块,通过无性能损耗的内存阈值监控来触发镜像采集。
将对象是否泄漏的判断延迟到了解析时,阈值监控只要在子线程定期获取关注的几个内存指标即可,性能损耗忽略不计。
KOOM 1.1.0版本源码整体结构
大致了解一下有什么东西
analysis:内存镜像解析模块,对hprof文件的分析
dump: 内存镜像采集模块,dump出hprof文件
monitor:内存监控模块
report: 文件报告模块
MonitorThread.java
- moniter.isTrigger()是否触发采集
- 进行轮询检测
isTrigger核心方法
触发的工作做完了,接下来就是做dump工作。
触发时机流程示意图
4.KOOM的高性能采集fork dump
像LeakCanary的 dump hprof是通过虚拟机提供的API dumpHprofData实现的,这个过程会“冻结”整个应用进程,导致几秒甚至10秒以上无法操作。
KOOM 相比较 LeakCanary 和 Matrix 来说有点不同,后俩者由于 dump 的整个过程会影响到主进程,所以基本应用与线下监控,而 KOOM 提出了 fork dump 的概念,能在 dump 分析内存泄漏的时候而不影响到主进程的应用运行,所以适合使用在线上监控。
KOOM高性能dump:
KOOM利用 Linux 的Copy-on-write机制(COW),fork子进程dump内存镜像,来解决这一问题,fork成功以后,父进程立刻恢复虚拟机运行,子进程dump内存镜像期间不会受到父进程数据变动的影响。
COW机制:
了解两个函数:fork()和exec(),exec是一组相关函数的统称,包括execl()、execlp()、execv()、execle()等。
fork()会创建一个子进程,子进程的是父进程的副本;
exec()重新装载程序,清空数据;
一般的fork()会直接将父进程的数据拷贝到子进程中,拷贝完之后,会执行exec(),父进程和子进程之间的数据段和堆栈是相互独立的。
Copy-on-write写时复制
为了节省fork子进程的内存消耗和耗时,fork出的子进程并不会copy父进程的内存,而是和父进程共享内存空间,父子进程只在发生内存写入操作时,系统才会分配新的内存为写入方保留单独的拷贝。
这就相当于子进程保留了fork瞬间时父进程的内存镜像,且后续父进程对内存的修改不会影响子进程。
上面的isTrigger之后到dump的流程,通过onTigger回调到dump流程
doHeapDump进入dump过程
对应KOOM核心代码ForkJvmHeapDumper.java的dump()
Copy-on-write的fork创建出的子进程,与父进程共享内存空间。既保留了镜像数据,同时子进程dump的过程也不会影响主进程执行。
KOOM fork dump流程示意图
实际fork过程:
暂停虚拟机需要调系统库,但谷歌从Android 7.0开始对调用系统库做了限制,基于此前提,快手自研了kwai-linker组件,绕过了这一限制
然后是解析流程
把结果add到GCPath
不同的Detector有不同的isLeak策略
5.采集性能对比
采集内存镜像需要暂停虚拟机,以确保在内存数据拷贝到磁盘的过程中,引用关系不会发生变化,暂停时间通常长达10秒以上,对用户来讲是难以接受的,这也是LeakCanary不推荐线上使用的重要原因之一。
KOOM利用Linux Copy-on-write机制fork子进程dump大大提高了dump效率。
线上真实用户的内存镜像,普通dump和fork子进程dump阻塞用户使用的耗时对比
耗时差距都在100倍以上
解析性能优化
KOOM没有采用LeakCanary1.0版本的HAHA解析引擎,使用HAHA解析过程中非常容易OOM,且解析速度极慢。LeakCanary2.0版本使用Shark新版解析引擎,KOOM基于Shark引擎进行解析。
兼容性
暂未支持Android R以上
只支持AndroidX,不支持Android Support Library
小结
内存阈值检测方式,将对象是否泄漏的判断延迟到了解析时,避免传统的频繁主动gc
使用COW机制来fork子进程做dump操作,能大大减少阻塞时长
附录
blog.csdn.net/Kwai_tech/a… 快手技术团队关于OOM治理
github.com/KwaiAppTeam… KOOM的github