](/img/bVc3ZXk)
01 背景
随着业务的倒退,百度APP有很多大内存业务场景如直播、短视频、小程序、百度识图等,通过线上页面统计数据得悉超过150M页面有40个,耗内存最多的页面有400M。单个页面不会有内存或者稳定性问题,然而当用户浏览了很多页面之后,累加起来内存曾经很高了,再加上咱们为了谋求秒开,常常采纳的思路是以空间换取工夫,从而导致APP处于一个内存高水位状态,在这种状况下如果关上一个大内存页面,中低端机极大概率会呈现OOM类型的解体。
内存管控计划应运而生,该计划重点解决的问题是在内存水位很高的状况下,保障APP稳定性又兼顾用户体验,缩短APP应用时长同时防止OOM。
该技术计划在百度APP于22年Q1顺利上线,随着根底服务层和越来越多的业务线接入,尤其是OOM频发的页面接入后,在升高OOM率方面施展了重大作用,成果非常明显。
02 技术计划综述
内存管控整体计划架构图如下所示:
- 实时监控APP内存:在APP运行阶段一直监控内存变动。重点关注两个问题,第一,选取适合的能反映APP内存的指标,第二、实时性必须满足要求,同时不能引入额定的性能问题。
- 页面内存预测:依据历史教训和线上数据,咱们能够预测将要关上新页面后APP占用内存大小,联合以后内存咱们能够实时计算出利用新开以后页面后本身占用的内存大小,举个例子,以后页面占用内存400M,通过线上历史教训数据咱们晓得新页面须要占用内存是300M,那么新开一个页面后,APP内存700M。
- 内存水位判断:依据对以后APP内存状态的监控,可能判断出用户内存所处的水位状态,如平安水位和危险水位,平安水位是指以后APP内存足够,可齐全依照业务需要分配内存,危险水位是指目前APP很容易呈现OOM,必须马上开释内存缓存。
- 频率管制:因为每隔3S做一次内存检测,当处于危险水位时,会告诉APP各个模块做内存开释,但内存开释也是须要工夫的,并且不肯定会立马升高到平安水位,如果接下来还是每隔3S告诉各模块做内存开释,其实是一种资源节约,频繁的内存开释操作会给APP性能带来损耗,所以通过频率管制模块既能最大限度地开释内存,又实现APP性能最小损失。
- 危险水位报警:当APP的内存处于危险水位的状态时,会向根底服务层和业务两个层面发送报警告诉,对于根底服务层来说,百度APP次要做了图片内存和NSURLCache内存主动回收,全局失效;对于业务层来说,次要针对内存小户且OOM率较高的页面做了内存开释操作,如小程序页面,收到内存报警时,会将缓存的处于非沉闷状态的页面做清理操作,对于其余业务同样情理,清理业务本身的数据缓存和其余内存缓存。
- 被动降级:是指业务层在调配较大内存时,先判断以后APP所属的内存水位等级,若处于危险水位,业务做降级调配较小内存,若处于平安水位,做全量内存调配。目前百度APP的识图和数字人业务已接入此计划,对于百度识图场景,做多模态图片辨认加载算法模型文件较大,处于危险水位时加载兜底模型,以业务能用为规范,其余场景相似。
03 与内存报警的区别
目前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危险。
04 实时监控APP内存
百度APP实时监控内存采纳如下计划:在子线程开启定时器,每隔3S去采样一次内存phys\_footprint字段数据,以此作为掂量的内存的惟一指标,其余字段值一律不要获取,因为多减少一个变量会多减少CPU计算量。实际数据表明,第一、单次获取phys\_footprint耗时小于1us,每隔3S获取phys\_footprint没有引起CPU占比的涨幅,也就是说不会带来性能问题;第二、3S的采样周期实时性齐全满足咱们工程的要求,失常状况下,开启一个页面到页面可交互须要1.5S+,采样周期如果太长,会存在页面内存曾经飙升然而还没来得及做管控,采样周期太短会节约过多的CPU资源。
为什么咱们选用phys\_footprint作为内存掂量指标,而不必其余字段,须要重点解释一下。iOS端所有的内存相干指标都集中在task\_vm\_info构造体中,下载XNU最新开源代码(https://opensource.apple.com/...),代码门路: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_tmemorystatus_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_table
phys\_footprint = (internal - alternate\_accounting) + (internal\_compressed - alternate\_accounting\_compressed) + iokit\_mapped + purgeable\_nonvolatile + purgeable\_nonvolatile\_compressed + page\_table 。
purgeable内存是iOS零碎为开发者提供的一层cache机制,分为volatile、empty和non\_volatile三种类型,volatile示意该内存资源是临时不被应用的,零碎将在内存吃紧的时候回收掉它,应用这种类型资源前要查问是否曾经有效了(变成empty状态);empty示意该内存资源明确不必了须要立刻开释;non\_volatile示意该内存资源始终有用,不能被回收。volatile和empty状态的资源不计入过程本人的mem footprint,它算零碎的cache内存,nonvolatile会算本人过程的内存,被虚拟内存零碎回收时不会被换出到磁盘,所以phys\_footprint在计算内存时,只计算了nonvolatile类型,对于volatile、empty没做计算。
05 页面内存预测
为了可能更精准的对页面的内存进行剖析和预测,咱们在实时内存监控的根底上,开发了页面内存预测计划。具体来说,在后面通过定时器咱们晓得了每隔3S手机APP内存状态,本计划通过教训数据间接预测将来一段时间内存的涨幅,让业务线能够更加从容的开释内存。咱们晓得当新关上一个页面时存在内存飙升的情景,这个时候3S的采样周期未到,内存曾经上涨很多,内存管控计划还未失效APP极有可能曾经OOM了。咱们的计划是通过页面内存计算,在关上新页面前一刻, 就晓得接下来页面内存可能会涨到多少,如果进入危险水位,实时开释内存以升高OOM率,通过这种精细化解决进一步提前升高内存峰值。
页面内存计算计划如下所示,首先,以后页面是P1页面,当有页面跳转产生,将要通过push操作进入到P2页面时,记录以后百度APP内存phys\_footprint值为M1,当从P2页面同样产生跳转到其余页面时,记录百度APP内存phys\_footprint值为M2,那么M2-M1为P2页面内存。
留神,咱们只通过push形式统计了页面内存,没有通过pop形式统计,有两个起因,第一、通过线上数据发现,pop形式时因页面曾经关上,并且会创立单例导致内存统计存在很多badcase,push形式时页面从未创立也不会有单例,数据绝对精确;第二、通过push形式曾经能够笼罩所有页面了,pop形式不须要统计。
06 制订内存水位
对于内存水位的制订间接决定了本计划理论收益的大小,水位阈值制订太小会导致频繁的内存管控影响业务成果,水位阈值制订的太大,与理论的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/...),代码门路: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#define MEMORYSTATUS_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) { case MEMORYSTATUS_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机制,更具备可操作性;同时联合本身线上数据,针对手百场景定制化开掘。
07 总结
最初,总结百度APP内存管控计划具备如下特点:
- 针对不同机型制订了相应的内存水位能够更加从容地开释内存。本技术计划联合Jetsam机制和百度APP线上内存数据,制订了iPhone各机型容许应用的内存水位线,给业务和框架更大的空间开释和清理内存。
- 实时内存监控和精细化页面内存预测,在实时内存监控的根底上,开发了页面级的内存度量计划,能够估算出用户在新开一个页面内存涨幅多少,在将来一段时间内存会不会达到危险水位。
- 内存管控计划提供被动和被动告诉两种形式获取内存水位状态,实现了各业务层依据手机内存状况实时降级,时效性更强,跟之前服务端降全量降级计划相比,更加灵便,性能更好。
该计划上线后,随着Q2根底服务层和业务线接入,实现OOM升高一半的收益,并且业务层接入老本很低,后续会推动更多内存小户和OOM频发的页面接入。感激各位浏览至此,如有问题请不吝指正。
———— END————
参考资料:
[1] OOM探索:XNU 内存状态治理:https://www.jianshu.com/p/445...
[2]XNU源码:https://opensource.apple.com/...
[3]《深刻解析Mac OS X & iOS操作系统》
[4]iOS Out-Of-Memory 原理论述及计划调研:https://juejin.cn/post/684490...
举荐浏览:
Ernie-SimCSE比照学习在内容反作弊上利用
品质评估模型助力危险决策程度晋升
合约广告平台架构演进实际
AI技术在基于危险测试模式转型中的利用
Go语言躲坑经验总结