一.
背景
随着业务的发展,百度APP有非常多大内存业务场景如直播、短视频、小程序、百度识图等,经过线上页面统计数据得知超过150M页面有40个,耗内存最多的页面有400M。单个页面不会有内存或稳定性问题,然则当用户浏览了非常多页面之后,累加起来内存已然很高了,再加上咱们为了追求秒开,经常采用的思路是以空间换取时间,从而引起APP处在一个内存高水位状态,在这种状况下倘若打开一个大内存页面,中低端机极重概率会显现OOM类型的崩溃。
内存管控方法应运而生,该方法重点处理的问题是在内存水位很高的状况下,保准APP稳定性又兼顾用户体验,延长APP运用时长同期避免OOM。
该技术方法在百度APP于22年Q1顺利上线,随着基本服务层和越来越多的业务线接入,尤其是OOM频发的页面接入后,在降低OOM率方面发挥了重大功效,效果非常显著。
二.
技术方法综述
内存管控整体方法架构图如下所示:
实时监控APP内存:在APP运行周期持续监控内存变化。重点关注两个问题,第1,选择合适的能反映APP内存的指标,第2、实时性必要满足需求,同期不可引入额外的性能问题。页面内存预测:按照历史经验和线上数据,咱们能够预测将要打开新页面后APP占用内存体积,结合当前内存咱们能够实时计算出应用新开当前页面后自己占用的内存体积,举个例子,当前页面占用内存400M,经过线上历史经验数据咱们晓得新页面必须占用内存是300M,那样新开一个页面后,APP内存700M。内存水位判断:按照对当前APP内存状态的监控,能够判断出用户内存所处的水位状态,如安全水位和危险水位,安全水位指的是当前APP内存足够,可完全根据业务需求分配内存,危险水位指的是日前APP很容易显现OOM,必要马上释放内存缓存。频率掌控:由于每隔3S做一次内存检测,当处在危险水位时,会通告APP各个模块做内存释放,但内存释放亦是必须时间的,并且不必定会马上降低到安全水位,倘若接下来还是每隔3S通告各模块做内存释放,其实是一种资源浪费,频繁的内存释放操作会给APP性能带来损耗,因此经过频率掌控模块既能最大限度地释放内存,又实现APP性能最小损失。危险水位报警:当APP的内存处在危险水位的状态时,会向基本服务层和业务两个层面发送报警通告,针对基本服务层来讲,百度APP重点做了照片内存和NSURLCache内存自动回收,全局生效;针对业务层来讲,重点针对内存大户且OOM率较高的页面做了内存释放操作,如小程序页面,收到内存报警时,会将缓存的处在非活跃状态的页面做清理操作,针对其他业务一样道理,清理业务自己的数据缓存和其他内存缓存。主动降级:指的是业务层在分配很强内存时,先判断当前APP所属的内存水位等级,若处在危险水位,业务做降级分配较小内存,若处在安全水位,做全量内存分配。日前百度APP的识图和数字人业务已接入此方法,针对百度识图场景,做多模态照片识别加载算法模型文件很强,处在危险水位时加载兜底模型,以业务能用为标准,其他场景类似。
三.
与内存报警的区别
日前iOS系统中存在类似的方法,专业名叫作为内存报警机制,当设备可用内存下降到到危险状态时,Mach系统的pageout 保护程序会查找进程列表及其驻留页面数,向驻留页面数最高的进程发送NOTE_VM_PRESSURE ,被选中的进程会响应这个压力通告,本质上便是APP收到系统的didReceiveMemoryWarning 内存警告,APP释放部分内存达到降低手机内存负载的目的。有人会问iOS系统供给了内存报警通告,为何咱们还会做貌似类似的事情,这是由于咱们对系统的内存报警机制做了如下两点弥补: 内存报警机制是内存极其危险的时候才发出的,尤其是针对低端机而言非常致命,由于APP来不及释放内存到安全水位就已然OOM了。在实践研发过程中,对低端机(iPhone8以下)测试结果发掘,当收到内存报警时,APP实质可运用内存(可用内存减去已用内存)无超过100M,然则日前手百APP大于150M页面就有40个,当收到内存警告前后,随便打开以上40个大页面中的任何一个页面,APP基本无来得及处理警报应用就会崩溃。相反,百度APP内存管控方法在制定危险水位时思虑到这种状况,适当预留了很强空间,让APP更从容地释放内存。内存报警机制无供给获取APP实时内存状态的功能,在实践中经常会遇到大块内存分配的场景,较为平常场景如在中低端机端智能场景中,加载大模型到内存时,由于不晓得内存当前处在危险状态还是安全状态,分配很强内存会显现内存峰值瞬时上涨到高点,中低端机手机设备直接OOM,在全部过程中亦基本无收到过内存报警。内存管控方法弥补了这一不足,经过实时获取内存状态,区别机型区别设备设置区别危险水位级别,在分配很强内存时,先判断APP内存状态,若处在危险水位时,业务线研发能够走降级规律,降低对内存消耗,减少OOM危害。
四.
实时监控APP内存
百度APP实时监控内存采用如下方法:在子线程开启按时器,每隔3S去采样一次内存phys_footprint字段数据,以此做为衡量的内存的独一指标,其他字段值一律不要获取,因为多增多一个变量会多增多CPU计算量。实践数据显示,第1、单次获取phys_footprint耗时少于1us,每隔3S获取phys_footprint无导致CPU占比的涨幅,亦便是说不会带来性能问题;第2、3S的采样周期实时性完全满足咱们工程的需求,正常状况下,开启一个页面到页面可交互必须1.5S+,采样周期倘若太长,会存在页面内存已然飙升然则还没来得及做管控,采样周期太短会浪费太多的CPU资源。
为何咱们选择phys_footprint做为内存衡量指标,而不消其他字段,必须重点解释一下。iOS端所有的内存关联指标都集中在task_vm_info结构体中,下载XNU最新开源代码
( https://opensource.apple.com/source/xnu/),代码路径:osfmk/mach/task_info.h,详细字段值如下所示:struct task_vm_info {
mach_vm_size_t virtual_size; /* virtual memory size (bytes) */
integer_t page_size;
mach_vm_size_t resident_size; /* resident memory size (bytes) */
/* 省略 */
mach_vm_size_t phys_footprint;
/* 省略 */
}iOS研发演变的这几年历程中,受Android端内存指标影响,咱们先后运用过各样内存指标,平常的如virtual_size( 虚拟内存)、resident_size(驻留内存)和phys_footprint,那到底运用哪个指标是恰当的?咱们晓得iOS运用的是低内存清理机制叫Jetsam,这个机制有点类似于Linux的“Out-of-Memory”杀手,当内存压力过大时,Jetsam会把有些优先级不高或占用内存过大的进程杀掉。便是说内存处在危险状态时Jetsam决定kill哪个进程,因此呢Jetsam衡量内存水位指标绝对是众多内存指标中最为恰当的一项,接下来咱们看Jetsam机制源码。
咱们再次回到XNU源码中,查看代码
bsd/kern/kern_memorystatus.c,重点查看函数 memorystatus_kill_hiwat_proc,这是jetsam核心代码,用于kill高内存分配进程的关键函数,详细实现如下所示:
static boolean_t
memorystatus_kill_hiwat_proc(uint32_t*errors,boolean_t *purged, uint64_t *memory_reclaimed)
{
next_p = memorystatus_get_first_proc_locked(&i, TRUE);
while (next_p) {
/* 省略 */footprint_in_bytes = get_task_phys_footprint(p->task);
skip = (footprint_in_bytes <= memlimit_in_bytes);if (skip) {
continue;
} else{
memorystatus_kill_proc(p, kMemorystatusKilledHiwat, jetsam_reason, &killed, &footprint_in_bytes);/* 省略 */
}
}首要经过 memorystatus_get_first_proc_locked去优先级队列里面取出优先级最低的进程,倘若内存超过阈值,将经过memorystatus_kill_proc杀掉这个进程,否则跳过取下一个进程。咱们看到Jetsam是经过 get_task_phys_footprint办法获取内存水位来决定是不是必须kill该进程,因此呢运用phys_footprint做为APP内存指标是最合适的。
关于 phys_footprint 的定义,咱们回到 XNU 源码中,查看代码 osfmk/kern/task.c ,有phys_footprint 的注释定义。 * Physical footprint: This is the sum of:
* + (internal - alternate_accounting)* + (internal_compressed - alternate_accounting_compressed)
* + iokit_mapped
* + purgeable_nonvolatile
* + purgeable_nonvolatile_compressed
* + page_tablephys_footprint = (internal - alternate_accounting) + (internal_compressed -
alternate_accounting_compressed) + iokit_mapped + purgeable_nonvolatile + purgeable_nonvolatile_compressed + page_table 。
字段
详细含义
internal
在iOS中暗示的便是resident_size驻留内存
internal_compressed
iOS 上无交换空间机制,取而代之运用Compressed memory,是在内存紧张时能够将近期运用过的内存占用压缩至原有体积的一半以下,并且能够在必须时解压复用
iokit_mapped
io设备映射占用的内存,其实是不可运用purgeable memory的部分
alternate_accounting
iokit映射占用的dirty页
page_table
虚拟位置映射表内存
purgeable_nonvolatile
下面重点介绍
purgeable内存是iOS系统为研发者供给的一层cache机制,分为volatile、empty和non_volatile三种类型,volatile暗示该内存资源是暂时不被运用的,系统将在内存吃紧的时候回收掉它,运用这种类型资源前要查找是不是已然无效了(变成empty状态);empty暗示该内存资源知道不消了必须立即释放;non_volatile暗示该内存资源始终有用,不可被回收。volatile和empty状态的资源不计入进程自己的mem footprint,它算系统的cache内存,nonvolatile会算自己进程的内存,被虚拟内存系统回收时不会被换出到磁盘,因此phys_footprint在计算内存时,只计算了nonvolatile类型,针对volatile、empty没做计算。
五.
页面内存预测
为了能够更精细的对页面的内存进行分析和预测,咱们在实时内存监控的基本上,研发了页面内存预测方法。详细来讲,在前面经过按时器咱们晓得了每隔3S手机APP内存状态,本方法经过经验数据直接预测将来一段时间内存的涨幅,让业务线能够更加从容的释放内存。咱们晓得当新打开一个页面时存在内存飙升的情景,这个时候3S的采样周期未到,内存已然上涨非常多,内存管控方法还未生效APP极有可能已然OOM了。咱们的方法是经过页面内存计算,在打开新页面前一刻, 就晓得接下来页面内存可能会涨到多少,倘若进入危险水位,实时释放内存以降低OOM率,经过这种精细化处理进一步提前降低内存峰值。
页面内存计算方法如下所示,首要,当前页面是P1页面,当有页面转出现,将要经过push操作进入到P2页面时,记录当前百度APP内存phys_footprint值为M1,当从P2页面一样发生转到其他页面时,记录百度APP内存phys_footprint值为M2,那样M2-M1为P2页面内存。
重视,咱们只经过push方式统计了页面内存,无经过pop方式统计,有两个原由,第1、经过线上数据发掘,pop方式时因页面已然打开,并且会创建单例引起内存统计存在非常多badcase,push方式时页面从未创建亦不会有单例,数据相对准确;第2、经过push方式已然能够覆盖所有页面了,pop方式不必须统计。
六.
制定内存水位
关于内存水位的制定直接决定了本方法实质收益的体积,水位阈值制定太小会引起频繁的内存管控影响业务效果,水位阈值制定的太大,与实质的Jetsam水位线偏离过大,引起内存管控没法生效,可能会显现APP已然OOM了,管控方法还没生效,水位线的制定非常关键。
关于危险水位线的制定,必要结合Jetsam原理,日前苹果官方无公开Jetsam水位的文档,业界有如下办法处理方法。
丨6.1 经过Jetsam日志获取
详细来讲从手机"设置->隐私->分析与改进->分析数据"这条操作路径中,能够拿到JetsamEvent 开头的日志。这些日志中就能够获取有些关于 App 的内存信息,查询崩溃原由时必须关注 per-process-limit 部分的 rpages,其中rpages表率进程占用的内存页数量。pageSize表率当前设备理学内存页的体积,在 JetsamEvent 开头的系统日志里能够找到 pageSize 的值,那样pageSize * rpage的值表率日前该进程OOM时运用的内存体积,可做为进程可用内存的上限。
实质操作过程中,发掘此办法可操作性不强,由于同一台手机区别的JetsamEvent日志rpages值变化太大,用iphone12的测试结果表示,从400到800都有,pageSize是固定值16384Byte,根据最高值计算当前 App 的内存限制值:pageSize * rpages / 1024 /1024 =16384 * 800 / 1024 / 1024 = 12.5M,按这个结果iphone12最大的内存阈值是12.5M,置信度显著有问题。
丨6.2经过XNU源码获取内存水位阈值
首要必要越狱手机获取root权限,经过XNU源码中的数据结构、宏定义和函数获取OOM阈值,参考XNU最新开源代码(https://opensource.apple.com/source/xnu/),代码路径:bsd/sys/kern_memorystatus.h,关键数据结构memorystatus_priority_entry,定义如下,其中pid表率进程标识,priority表率JetSam中的优先级,limit便是咱们要找的水位线上线。同期,在文件kern_memorystatus.h有如下跟进程优先级关联的宏命令,其中经过MEMORYSTATUS_CMD_GET_PRIORITY_LIST宏定义能够获取进程的优先级列表以及每一个进程的内存水位线。 typedef struct memorystatus_priority_entry {
pid_t pid;
int32_t priority;
uint64_t user_data;
int32_t limit;
uint32_t state;
} memorystatus_priority_entry_t;
#define MEMORYSTATUS_CMD_GET_PRIORITY_LIST 1
#define MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES 2
#defineMEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT 3
#define MEMORYSTATUS_CMD_GET_PRESSURE_STATUS 4
/* 省略 */
最后经过调用系统函数memorystatus_control的实现可获取memorystatus_priority_entry结构体值,其中limit字段表率水位线, 代码路径:bsd/kern/kern_memorystatus.c int memorystatus_control(struct proc *p __unused, struct memorystatus_control_args *args, int *ret) { /* 省略 */
switch (args->command) {
caseMEMORYSTATUS_CMD_GET_PRIORITY_LIST:
error = memorystatus_cmd_get_priority_list(args->buffer, args->buffersize, ret);break;
/* 省略 */
}实践证明这种办法是可行的,独一缺点是必须获取root权限,咱们要获取区别机型的内存阈值,必须将这些设备所有越狱。
丨6.3百度APP采用的技术方法
百度APP采用的方法是综合百度APP自己的线上业务数据,采用主动触发OOM获取内存阈值方法,结合多方数据最后确定内存危险水位阈值。
丨6.3.1内存数据摸底
经过线上内存采样打点,获取了百度APP区别机型在运用过程中的内存值,而后经过服务端数据聚合,咱们知道晓得了百度APP在无出现OOM状况下区别机型的内存最大值,这份线上数据很重要,虽然不是内存阈值的,然则内存阈值肯定是高于该值的。
丨6.3.2页面内存数据统计
技术方法在第五节做过仔细介绍,这儿再也不赘述,经过服务端对页面内存数据挖掘后,咱们知道晓得了区别机型新开一个页面时最大的内存涨幅。
丨6.3.3主动触发OOM获取内存值
开启按时器任务每隔1S分配20M内存,示例代码如下所示: int size = 20 * 1024 * 1024;
char *info = malloc(size);memset(info, 1, size);同期监控内存变化,在掌控台输出,随着可用内存越来越少,触发Jetsam机制,直到出现OOM,从而得到OOM前内存阈值。 (int64_t)memoryUsage {
int64_t memoryUsageInByte = 0;
struct task_vm_info info;
mach_msg_type_number_t size = TASK_VM_INFO_COUNT;
kern_return_t kerr = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &info, &size);
if(kerr == KERN_SUCCESS ) {
memoryUsageInByte = info.phys_footprint;
}return memoryUsageInByte;
}丨6.3.4确定内存管控危险水位阈值
经过前面三个过程,咱们获取了区别机型的三个阈值,分别是内存数据摸底阈值、页面内存阈值、主动触发OOM获取的阈值,为了让业务更从容地释放内存, 内存管控阈值为主动触发OOM获取的阈值减去页面内存阈值,倘若该值少于内存数据摸底阈值,那样内存数据摸底阈值便是该机型内存管控阈值。
百度APP采用的这个技术方法不必须越狱手机,经过主动触发OOM获取的阈值表现了Jetsam机制,更拥有可操作性;同期结合自己线上数据,针对手百场景定制化挖掘。
七.
总结
最后,总结百度APP内存管控方法拥有如下特点: 针对区别机型制定了相应的内存水位能够更加从容地释放内存。本技术方法结合Jetsam机制和百度APP线上内存数据,制定了iPhone各机型准许运用的内存水位线,给业务和框架更大的空间释放和清理内存。实时内存监控和精细化页面内存预测,在实时内存监控的基本上,研发了页面级的内存度量方法,能够估算出用户在新开一个页面内存涨幅多少,在将来一段时间内存会不会达到危险水位。内存管控方法供给主动和被动通告两种方式获取内存水位状态,实现了各业务层按照手机内存状况实时降级,时效性更强,跟之前服务端降全量降级方法相比,更加灵活,性能更好。
该方法上线后,随着Q2基本服务层和业务线接入,实现OOM降低一半的收益,并且业务层接入成本很低,后续会推动更加多内存大户和OOM频发的页面接入。感谢各位阅读至此,如有问题请不吝指正。
八.
参考链接
[1] 、OOM探究:XNU 内存状态管理:https://www.jianshu.com/p/4458700a8ba8
[2]、XNU源码:https://opensource.apple.com/source/xnu/
[3]、《深入解析Mac OS X & iOS操作系统》
[4]、iOS Out-Of-Memory 原理阐述及方法调研:https://juejin.cn/post/6844903749836603400#heading-7
作者:RichardYang
源自:微X公众号:百度App技术
出处 :https://mp.weixin.qq.com/s/dETOGD3NYU2SdZhxGu0SZg
|