背景算法
根據Apple官方WWDC的回答,減小內存可讓用戶體驗到更快的啓動速度,不會由於內存過大而致使Crash,可讓APP存活的更久。緩存
對於高德地圖來講,根據線上數據的分析,內存太高會致使導航過程當中系統強殺OOM。尤爲區別於其餘APP的地方是,通常APP只須要關注前臺內存太高的系統強殺FOOM,高德地圖有很多用戶使用後臺導航,因此也須要關注後臺的內存太高致使的系統強殺BOOM,且後臺強殺較前臺強殺更爲嚴重。爲了提高用戶體驗,內存治理迫在眉睫。markdown
原理剖析app
OOM框架
OOM是Out of Memory的縮寫。在iOS APP中若是內存超了,系統會把APP直接殺死,一種另類的Crash,且沒法捕獲。發現OOM時,咱們能夠從設備->隱私->分析與改進->分析數據中找到以JetsamEvent開頭的日誌,日誌裏面記錄了不少信息:手機設備信息、系統版本、內存大小、CPU時間等。函數
Jetsam工具
Jetsam是iOS系統的一種資源管理機制。不一樣於MacOS、Linux、Windows等,iOS中沒有內存交換空間,因此在設備總體內存緊張時,系統會將一些優先級不高或者佔用內存過大的直接Kill掉。性能
經過iOS開源的XNU內核源碼能夠分析到:測試
Jetsam機制清理策略能夠總結爲如下幾點:優化
Android端爲Low Memory Killer:
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分析工具等。
治理優化
根據上面的數據分析,很容易作出從大頭開始抓起的思路。咱們在治理過程當中的大致思路:
分而治之
據數據分析,高德地圖三大內存消耗分別是地圖渲染(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 writing
typedef 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分析工具。