背景

依据Apple官网WWDC的答复,缩小内存能够让用户体验到更快的启动速度,不会因为内存过大而导致Crash,能够让APP存活的更久。

对于高德地图来说,依据线上数据的剖析,内存过高会导致导航过程中零碎强杀OOM。尤其区别于其余APP的中央是,个别APP只须要关注前台内存过高的零碎强杀FOOM,高德地图有不少用户应用后盾导航,所以也须要关注后盾的内存过高导致的零碎强杀BOOM,且后盾强杀较前台强杀更为严重。为了晋升用户体验,内存治理火烧眉毛。

原理分析

OOM

OOM是Out of Memory的缩写。在iOS APP中如果内存超了,零碎会把APP间接杀死,一种另类的Crash,且无奈捕捉。发现OOM时,咱们能够从设施->隐衷->剖析与改良->剖析数据中找到以JetsamEvent结尾的日志,日志外面记录了很多信息:手机设施信息、零碎版本、内存大小、CPU工夫等。

Jetsam

Jetsam是iOS零碎的一种资源管理机制。不同于MacOS、Linux、Windows等,iOS中没有内存替换空间,所以在设施整体内存缓和时,零碎会将一些优先级不高或者占用内存过大的间接Kill掉。

通过iOS开源的XNU内核源码能够剖析到:

  • 每个过程在内核中都存在一个优先级列表,JetSam在受到内存压力时会从优先级列表最低的过程开始尝试杀死,直到内存水位复原到失常水位。
  • Jetsam是通过get_task_phys_footprint获取到phys_footprint的值,来决定要不要杀掉利用。

Jetsam机制清理策略能够总结为以下几点:

  • 单个APP物理内存占用超过下限会被清理,不同的设施内存水位线不一样。
  • 整个设施物理内存占用受到压力时,优先清理后盾利用,再清理前台利用。
  • 优先清理内存占用高的利用,再内存占用低的利用。
  • 相比零碎利用,会优先清理用户利用。

Android端为Low Memory Killer:

  • 依据APP的优先级和应用总内存的多少,零碎会在设施内存吃紧状况下强杀利用。
  • 内存吃紧的判断取决于零碎RSS(理论应用物理内存,蕴含共享库占用的全副内存)的大小。
  • 要害参数有3个:

1)oom_adj:在Framework层应用,代表过程的优先级,数值越高,优先级越低,越容易被杀死。

2)oom_adj threshold:在Framework层应用,代表oom_adj的内存阈值。Android Kernel会定时检测以后残余内存是否低于这个阀值,若低于则杀死oom_adj ≥该阈值对应的oom_adj中,数值最大的过程,直到残余内存复原至高于该阀值的状态。

3)oom_score_adj:在Kernel层应用,由oom_adj换算而来,是杀死过程时理论应用的参数。

数据分析

phys_footprint获取iOS利用总的物理内存,具体能够参考官网阐明iOS Memory Deep Dive.

std::optional<size_t> memoryFootprint(){    task_vm_info_data_t vmInfo;    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;    kern_return_t result = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);    if (result != KERN_SUCCESS)        return std::nullopt;    return static_cast<size_t>(vmInfo.phys_footprint);}

Instruments-VM Tracker能够用来剖析具体内存分类,比方Malloc局部是堆内存,Webkit Malloc局部是JavaScriptCore占用的内存等。须要留神的是每个分类的内存值 = Dirty Size + Swapped。

通过Instruments VM Tracker抓取导航中内存散布进行比照剖析。导航前台静置时,高德地图的总内存数值十分高,其中IOKit、WebKit Malloc和Malloc堆内存为内存占用大头。

在剖析过程中能够应用的工具很多,各有优缺点,须要配合应用,互相补救。咱们在剖析的过程中次要用到Intruments VM Tracker、Allocations、Capture GPU Frame、MemGraph、dumpsys meminfo 、Graphics API Debugger、Arm Mobile Studio、AJX 内存剖析工具、自研Malloc剖析工具等。

  • IOKit内存为地图渲染显存局部。
  • WebKit Malloc内存为AJX JS业务内存。
  • Malloc堆内存,咱们通过Hook Malloc分配内存的API,通过抓取堆栈剖析具体内存消费者。

治理优化

依据下面的数据分析,很容易做出从大头开始抓起的思路。咱们在治理过程中的大体思路:

  • 剖析数据:从内存大头开始,剖析各内存归属业务,以便业务进一步剖析优化。
  • 内存治理:优化技术计划缩小内存开销、高下端机性能分级和智能容灾(即内存告警时通过性能降级等策略开释内存)。

分而治之

据数据分析,高德地图三大内存耗费别离是地图渲染(Graphic显存)、性能业务(JavaScriptCore)和通用业务(Malloc)。咱们也次要从这三个方面动手优化。

地图Graphic显存优化

Xcode自带Debug工具Capture GPU Frame,能够剖析出具体显存占用,显存次要分为纹理Texture局部和Buffer局部,通过具体的地址信息剖析具体耗费。Android端相似剖析显存工具能够用Google的Graphics API Debugger。

依据剖析,Texture局部咱们通过FBO绘制形式调整、矢量路口大图背景优化、图标跨页面开释、文字纹理优化、低端机关闭全屏抗锯齿等缩小显存耗费。Buffer局部通过开启低显存模式、敞开四叉树预加载、切后盾开释缓存资源等。

Webkit Malloc优化

高德地图应用的是自研的动态化计划,依赖于iOS零碎提供的框架JavaScriptCore,应用的业务内存耗费大多会被零碎归类到WebKit Malloc,从零碎工具Instruments上的VM Tracker能够看出。此处有两个思路,一个是业务本身优化内存耗费,第二个是动态化引擎和框架优化内存耗费。

业务本身优化,动态化计划的IDE提供内存剖析工具能够清晰的输入具体业务内存耗费在什么中央,便于业务同学剖析是否正当。

动态化引擎和框架优化,咱们通过优化对系统库JavaScriptCore的应用形式,即多个JSContextRef上下文共享同一份JSContextGroupRef的形式。多个页面能够共享一份框架代码,从而缩小内存开销。

Malloc堆内存优化

iOS端堆内存调配基本上应用的libmalloc库,其中蕴含以下几个内存操作接口:

// c调配办法void    *malloc(size_t __size) __result_use_check __alloc_size(1);void    *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);void     free(void *);void    *realloc(void *__ptr, size_t __size) __result_use_check __alloc_size(2);void    *valloc(size_t) __alloc_size(1);// block调配办法// Create a heap based copy of a Block or simply add a reference to an existing one.// This must be paired with Block_release to recover memory, even when running// under Objective-C Garbage Collection.BLOCK_EXPORT void *_Block_copy(const void *aBlock)    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

通过hook内存操作API记录下内存调配的堆栈、大小,即可剖析内存应用状况。

同时源码中还存在一个全局钩子函数malloc_logger ,可输入Malloc过程中的日志,定义如下:

// We set malloc_logger to NULL to disable logging, if we encounter errors// during file writingtypedef void(malloc_logger_t)(uint32_t type,        uintptr_t arg1,        uintptr_t arg2,        uintptr_t arg3,        uintptr_t result,        uint32_t num_hot_frames_to_skip);extern malloc_logger_t *malloc_logger;

iOS堆内存剖析计划,可通过hook malloc系列API,也能够设置malloc_logger的函数实现,即可记录下堆内存应用状况。

此计划有几个难点问题,每秒钟内存调配的量级大、内存有调配有开释须要高效查问和堆栈反解聚合。为此咱们设计了一套残缺的Malloc堆内存剖析计划,来满足疾速定位堆内存归属,以便散发到各自业务Owner剖析优化。

对立治理

随着业务的增长给高德地图这个超级APP带来了极大资源压力,因而咱们积淀了一套自适应资源管理框架,来满足不同业务场景在无限资源下可能做到性能和体验极致平衡。次要的设计思路是通过监测用户设施等级、零碎状态、以后业务场景以及用户行为,利用调度算法进行实时推算,对立治理协调APP以后资源状态调配,对用户以后不可见的内存等资源进行回收。

自适应资源管理框架-内存局部

能够依据不同的设施等级、业务场景、用户行为和零碎状态来治理资源。各业务都能够很容易的接入此框架,目前曾经利用到多个业务场景,均有不错的收益。

数据验收

通过三个版本的间断治理,前后台导航场景均有50%的收益,同时Abort率也有10%~20%的收益。整体收益算是比拟乐观,然而随之而来的挑战是咱们该如何守住成绩。

长线管控

所谓打江山容易守江山难,如果没有长线管控的计划,随着业务的版本迭代,不出三五个版本就会将先前的优化耗费。为此咱们构建了一套APM性能监控平台,在研发测试阶段发现并解决问题,不把问题带上线。

APM性能监控平台

为了将APP的性能做到日常监控,咱们建设了一套线下「APM性能监控平台」,平台可能反对惯例业务场景的性能监控,包含:内存、CPU、流量等,可能及时的发现问题并进行报警。再配合性能跟进流程,为客户端性能保障把好最初一关。

内存剖析工具

Xcode memory gauge:在Xcode的Debug navigator中,能够粗略查看内存占用的状况。

Instruments - Allocations:能够查看虚拟内存占用、堆信息、对象信息、调用栈信息、VM Regions信息等。能够利用这个工具剖析内存,并针对地进行优化。

Instruments - Leaks:用于检测内存透露。

Instruments - VM Tracker:能够查看内存占用信息,查看各类型内存的占用状况,比方dirty memory的大小等等,能够辅助剖析内存过大、内存透露等起因。

Instruments - Virtual Memory Trace:有内存分页的具体信息,具体能够参考WWDC 2016 - Syetem Trace in Depth。

Memory Resource Exceptions:从Xcode 10开始,内存占用过大时,调试器能捕捉到EXC_RESOURCE RESOURCE_TYPE_MEMORY异样,并断点在触发异样抛出的中央。

Xcode Memory Debugger:Xcode中能够间接查看所有对象间的相互依赖关系,能够十分不便的查找循环援用的问题。同时,还能够将这些信息导出为memgraph文件。

memgraph + 命令行指令:联合上一步输入的memgraph文件,能够通过一些指令来剖析内存状况。vmmap能够打印出过程信息,以及VMRegions的信息等,联合grep能够查看指定VMRegion的信息。leaks可追踪堆中的对象,从而查看内存透露、堆栈信息等。heap会打印出堆中所有信息,不便追踪内存占用较大的对象。malloc_history能够查看heap指令失去的对象的堆栈信息,从而不便地发现问题。

总结:malloc_history ===> Creation;leaks ===> Reference;heap & vmmap ===> Size。

MetricKit:iOS 13新推出的监控框架,用于收集和解决电池和性能指标。当用户应用APP的时候,iOS会记录各项指标,而后发送到苹果服务端上,并主动生成相干的可视化报告。通过Window -> Organizer -> Metrics可查,包含电池、启动工夫、卡顿状况、内存状况、磁盘读写五局部。也能够MetricKit集成到工程里,将数据上传到本人的服务进行剖析。

MLeaksFinder:通过判断UIViewController被销毁后其子view是否也都被销毁,能够在不入侵代码的状况下检测内存透露。

Graphics API Debugger:Google开源的一系列的Graphics调试工具,能够查看、微调、重播利用对图形驱动的API调用。

Arm Mobile Studio: 专业级GPU剖析工具。