【转载】手Q Android缓存监控与优化实践

作者:jcmp      发布时间:2021-04-23      浏览量:0
一、一、背景对于Android应用来说,

一、一、背景

对于Android应用来说,内存向来是比较重要的性能指标。内存占用过高,会影响应用的流畅度,甚至引发OOM,非常影响用户体验。因此,内存优化也向来是行业内的重点工作项和难点工作项。

手Q在很早之前就开发了很多内存优化技术:

以上这些技术都取得了很好的内存优化效果,但他们的特点是:主要针对明显的内存问题,缺少更深入的内存分析。因此,手Q内存问题也一直存在,主要表现在以下两方面:1,手Q的平均内存一直持续增长,版本间增幅较高。手Q一月一个版本,平均每版本增长大概5.3M;2,用户的OOM率大概0.1%。

这次我们主要从监控和清理两个角度出发,系统化的进一步优化手Q内存:

1.统一缓存监控 开发实现全面的内存缓存监控系统,能够更细致的监控手Q内存缓存使用情况,及时发现轻度不合理问题,推进优化。

2.内存清理 在监控的基础上,开发实现自动清理机制。一方面统一调度手Q各业务主动清理内存,另一方面,通过深入的技术研究,实现系统内存清理技术。

通过监控和清理相互配合,我们最终实现了优化手Q整体内存,降低OOM率的效果。以下是详细方案。

二、二、统一缓存监控

2.1、图片缓存监控

对于Android应用来说,Bitmap向来是内存的占用大户。在手Q中平均有300+ Bitmap对象。统计显示:Bitmap引用内存占手Q总内存40%左右:

图片缓存监控使用内存快照技术实现,分为终端数据采集和后台数据分析两部分。流程如下所示:

终端数据采集: 客户端实时检测当前可用内存,当可用内存不足时,自动生成内存快照文件,上报到后台。

后台数据分析: 在后台,我们实现了一套Hprof文件分析以及Bitmap引用归并技术,批量分析内存快照文件,输出Bitmap引用链并进行归类统计,过滤全局图片专用缓存以及View层引用后,分析出存在图片缓存私藏的业务。

实现图片缓存监控过程中我们主要遇到以下几个难点:

通过图片监控系统,我们有效检测出以下几类业务问题:

在手Q730版本,图片缓存监控系统检测出32例业务问题,提单26例,累计节省内存约23M。

2.2、业务对象缓存监控

业务对象缓存监控主要是通过实现自定义集合类,实时上报各业务内存缓存使用情况到后台。在后台分析归并,从而定位业务缓存问题。

如上图所示,业务对象缓存主要分为终端数据采集、后台数据分析、缓存清理三部分。

后台分析: 分析终端上报的用户数据,对各业务缓存进行归类统计,统计出平均内存占用,最大内存占用,内存占用中位数,缓存命中率,缓存浪费率等指标。

内存清理: 监控系统在监控的基础上,也增加了清理接口。当检测到当前可用堆内存比较低,用户处于内存高负荷状态时,统一调度清理逻辑,进行内存自清理优化。

通过统一缓存监控,我们检测出了很多业务缓存问题,主要可归为以下三类:

三、三、内存清理

统一监控,可以有效发现业务缓存问题进行专项优化。但监控具有一定的滞后性,因此在监控的基础上,我们同时也增加了内存清理控制模块。

内存清理主要分为业务内存清理以及系统内存清理。业务内存清理,包含统一图片缓存清理,以及业务缓存对象清理两部分。这里前文已简单介绍。接下来我们介绍下两例系统相关的内存清理技术: 系统ClassLoader内存清理和系统预加载图片清理 。

3.1、系统ClassLoader内存清理

前期,我们分析了很多内存快照,发现一个共性的问题:在内存快照中有个ZipFile对象,内存占用一直超过2.6M。这个zipFile被系统类ClassLoader引用。

通过分析系统源码,我们发现ZipFile记录了安装包所有的类文件信息,手Q安装包中有超过15000个文件,文件越多,zipFile占用内存就越大。

我们进一步分析ClassLoader相关源码,发现只有在调用ClassLoader的findResource方法查找图片等安装包内资源时,才会使用到ZipFile的内容,未发现其他使用场景。同时,通过findResource方式查找资源存在一定的弊端:耗时很长,在Android系统上不推荐使用。 详情分析可参考:

http://blog.nimbledroid.com/2016/04/06/slow-ClassLoader.getResourceAsStream-zh.html。

综合评估,可以清理ClassLoader引用的这块内存。清理主要面临以下几个难点:

下图是我们清理系统ClassLoader的实现方案,采用代理,兜底,缓存,上报等手段逐一攻克以上难点,完美实现清理系统ClassLoader内存的效果。

1.针对兼容性问题,我们通过反射代理替换了系统的ZipFile为HookZipFile,替换完成后,清理掉zipFile内存。替换机制兼容系统不同版本以及特殊机型,对系统逻辑无影响。

2.针对清理导致的功能异常,我们实现了兜底能力,下次访问时,会重新创建zipFile。

3.针对耗时问题,内部封装实现缓存功能。并针对业务访问增加堆栈上报,及时推动业务改用其他方式获取资源。

内存清理方案,通过内部兼容性测试,发布后外网无crash问题,通过不断迭代,兼容率达到100%。并且内存清理效果明显,平均清理内存量约2.6M。

3.2、系统预加载图片清理

系统预加载图片缓存是zygote进程初始化时,通过preloadResources()预加载的通用图片资源,后续android应用进程都是从Zygote fork出来的,所以就继承了这部分预加载的图片资源。由于是静态强引用,这部分图片资源会一直占用内存空间。

预加载的好处在于系统只在zygote执行一次加载操作,所有应用用到该资源不需要再重新加载,减少资源加载耗时。与此同时,sPreloadedDrawables属于静态对象,会一直引用图片缓存,所以该系统机制会占用较高内存,在有些系统上,内存占用空间超过20M。

因此这里存在内存优化的空间,当内存占用高时,可以主动清理掉这部分内存,以便释放可观的堆内存空间,减少内存耗尽的风险。通过分析drawable加载机制的源码,我们了解到如果预加载的资源没有在sPreloadedDrawables中找到,会重新decode解码加载,不会影响现有功能。因此清理后的风险可控,主要面临的难点是兼容性问题:

下图是我们清理系统预加载图片缓存的实现方案,通过反射替换的方式,拦截替换系统的预加载缓存为自定义图片缓存,内部管理图片加载,在内存不足时,及时清理预加载图片缓存。

针对兼容性问题,我们实现了一套完备的兼容性方案:包含1,替换前兼容检测;2,系统版本兼容处理;3,特定机型兼容处理;4,失败上报统计,不断兼容。

系统预加载图片清理,通过不断迭代,已经可以兼容几乎所有机型。版本发布后,未引入系统功能异常以及外网crash问题。内存清理量比较可观:平均在15M左右,最高达到25M。

四、四、优化效果

4.1、横向对比

在灰度阶段,我们对用户进行了ABTest测试,一半用户接入内存优化逻辑,一半用户不接入。大盘上报统计显示:优化用户OOM率明显低于未优化用户:OOM率由0.09%降至0.053%。

内存分布方面,内存优化显著降低了高内存用户占比。由3.05%降至1.7%。高内存用户是指当前可用内存不足20%的用户,是OOM高发用户群,降低这部分用户比例,可有效降低OOM率。

4.2、纵向对比

我们从7.3.0版本接入内存优化,从版本迭代来看,优化效果显著:OOM率由0.09%左右降低至0.047%左右,降幅47%。

手Q版本间平均内存增幅明显放缓,版本增幅由5.8M左右降至1.14M左右。