前面几节我们主要介绍了线下场景的监控,线下场景不管是 android studio 还是 MAT 或者 adb命令都可以直接使用,但线上场景就不行了,一是不能直接连接,二是设备数量众多,不可能每台都看,所以我们采用两步走的”远程坐诊”方式来解决这个问题。(因为UI和CPU基本在线下场景就能解决,故本节我们主要针对内存监控进行介绍)。
可分为两步
第一步,通过代码将设备的基本信息记录到日志中,如果低于预期可以报警
第二步,针对报警的设备dump出详细内存信息供下载查看
第一步
内存:
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); am.getMemoryInfo(memInfo );
long deviceMemory = memInfo .totalMem;
long freeMemory = memInfo .availMem;
long threshold = memInfo .threshold;
long lowMemory = memInfo .lowMemory;
或者:
最大内存: Runtime.getRuntime().maxMemory();
当前使用内存: Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
CPU不能直接通过API获取,是通过读取系统内的文件信息获得,代码就不贴出来了,大家可以参考:
https://www.jianshu.com/p/6bf564f7cdf0
其它信息包括网络,电量根据实际情况也可以记录一下。
第二步:
如果第一步收集的信息(主要是内存)低于阈值(自己设定),那么就dump出内存快照,一行代码即可:
Debug.dumpHprofData(filePath)
(其具体实现在源码 art/runtime/hprof/hprof.cc 中);
先将快照输出至指定目录,然后再上传至服务器即可(自己实现),接着再将此文件下载到本地,有了这个文件,后面的操作就和 线下场景的内存一模一样了,通过hprof-conv.exe命令转换后,导入到工具里查看即可,就像是你把自己的血液样本(其它样本也可)寄给了千里之外的医生,即使不在你的身旁,也能用仪器检测出病因。
直接使用dumpHprofData这个方法会引起进程冻结,应用会卡顿几秒,体验很不好,快手的团队针对这种情况,给出了优化的解决方案:fork出一个一模一样的进程(叉子进程),在新进程进行dump,从而不影响正在使用的进程。这个就涉及到native层的操作了,大家可参考快手的解决OOM的开源库:KOOM 。
https://baijiahao.baidu.com/s?id=1674693121890730020&wfr=spider&for=pc
除了用代码生成内存快照外,使用adb命令也可以,并且这种方式可以直接导入到as中无需进行格式转换(MAT还是要转换的)。但是因为众多的手机厂商对系统都进行了定制化的修改,所以这种方式兼容性并不好,在一些机型上可能失效。
adb shell am dumpheap pid /data/local/tmp/x.hprof (pid替换成你应用的进程id)
最后,还有一点要注意,因为dump函数比较耗时,在发生OOM之后再去执行dump操作,可能捕获不到有效信息,最好是在OOM发生前进行dump,用第一步的方式定时去查询内存,如果超过阈值,那么就dump,不要等到异常发生了才去dump。
Tips:
1.Dump后的.hprof往往很大,我实测会有4,50M左右(仅供参考),这对流量,存储都提出了很高的要求,这又引申出了一个概念:裁剪 hprof 文件,它的目的是去除一些无关紧要的信息,让其体积减少,便于网络传输和存储。Hprof文件也是遵循一定的协议或者说格式(如下图),基于这个对其修改,达到裁切的目的。
可以参考西瓜视频的内存快照裁剪压缩工具 Tailor
https://blog.csdn.net/ByteDanceTech/article/details/111189304
或者美团的Probe
https://tech.meituan.com/2019/11/14/crash-oom-probe-practice.html
2.我们熟知的LeakCanary核心也用到了dumpHprofData来生成内存快照,位于独立的HeapAnalyzerService进程,而解析是由开源项目HAHA(https://github.com/square/haha)来完成。
dumpHprofData方法这个只是获取Java堆的信息,应用中还包含了 Native内存的信息,这部分内存是由业务动态申请的内存,一般是业务so库,业务代码是c/c++实现的,常用的方式就是调用 malloc函数申请内存,调用free释放内存。这些内存的申请都需要合理的释放,否则会导致内存不足。可结合Debug.getMemoryInfo()以及/proc/pid/smap文件来分析。
关于线上native内存的泄漏监控,我们可以参考西瓜视频Raphael: https://github.com/bytedance/memory-leak-detector
最后,我们来整体看下线上场景APP监控的流程(引用了腾讯全民K歌的整体分析流程):
基本流程是通过监控系统得出记录文件 再交给本地分析和后台分析,做的好的,可以将分析结果与项目管理软件相关联,直接将BUG指派到人。
总的来说,我们不是只懂得一些内存泄露解决方法就可以,更重要的是通过日常测试与监控,得到内存泄露检测与修改的一整套闭环体系。有了这一套体系或者流程,开发测试的效率提高,应用的质量得到极大的保证。当然,对于很多中小公司来说,这么做可能有点本末倒置了,实际中需要去衡量投入与产出比及带来的价值。
总结:
1.线上的监控相比线下更有难度,只有个别实力雄厚的大厂才能建立一套完整的闭环体系。我们要结合自身的情况去做监控方案,否则就得不偿失了。
2.监控功能的开关和灰度很重要,最好能做到可控,一旦监控本身有问题,可以及时关闭。
微信甚至上过一种兜底方案:在内存逼近临界值时,把自己kill掉。。。也就是说把本来的系统行为提前转为自己的应用行为了,充分体现了控制反转的思想。但这种做法风险系数很高,只能针对一小部分用户灰度。
3.线上和线下结合才能包治百病。