iOS記憶體的深入探究(WWDC 2018 session 416)
首先裝置硬體資源是固定的,所以app的記憶體資源是有限的。較低的記憶體佔用可以提高使用者體驗以及效能。如果記憶體佔用過大,可能會被系統殺掉。所以每個開發者都應該注意記憶體問題。本session主要分為以下幾方面:
為什麼要減少記憶體佔用
記憶體佔用
分析記憶體佔用的工具
影象
在後臺時,對記憶體的優化
演示demo
答案很簡單,為了更好的使用者體驗。減少記憶體佔用能同時減少其對CPU時間維度上的消耗,從而不僅使您所開發的App,其他App以及整個系統也都能表現的更好。
並非所有的記憶體佔用都是相等的。而要減少的記憶體佔用其實指的是虛擬記憶體(Virtual Memory) 佔用。
Pages
記憶體是由系統管理,一般以頁為單位來劃分。
在iOS 上,每一頁包含16KB的空間。系統會按照頁來分配記憶體,堆上可能會有多個物件在一頁上,也可能一個物件佔用多頁。
所佔用頁總數乘以每頁空間得到的就是這段資料使用的總記憶體。
記憶體頁按照各自的分配和使用狀態,可以分為Clean和Dirty兩類。
舉個例子,如果我申請了一個20000個整型的陣列(80000個位元組)。系統可能會分配給我6頁記憶體。
當我申請空間後,他們都是Clean的
如果我在陣列的第一個位置寫入資料,那麼該頁就會變Dirty了
如果我在陣列最後一個位置寫入資料,那麼該頁就會變Dirty了。
中間的幾頁都是Clean的,因為他們還未被寫入。
記憶體對映檔案
當 App 訪問一個檔案時,系統核心會負責排程,將磁碟上的檔案載入並對映到記憶體中。如果這是隻讀的檔案,它所佔用到的記憶體頁是Clean的。
如下圖所示,一個50KB的圖片被載入到記憶體中時,需要分配4頁記憶體來儲存。其中第四頁中有2KB的空間會被用來儲存這個圖片的資料,剩餘空間可能會被用來儲存其它資料。前三頁總是可以被系統清除的。
典型app記憶體型別
當記憶體不足的時候,系統會按照一定策略來騰出更多空間供使用,比較常見的做法是將一部分低優先順序的資料挪到磁碟上,這個操作稱為Page Out。之後當再次訪問到這塊資料的時候,系統會負責將它重新搬回記憶體空間中,這個操作稱為Page In。
Clean Memory
Clean Memory是指那些可以用以Page Out的記憶體,只讀的記憶體對映檔案,或者是App所用到的frameworks。每個frameworks都有_DATA_CONST段,通常他們都是Clean的,但如果用runtime進行swizzling,那麼他們就會變Dirty。
Dirty Memory
Dirty Memory是指那些被App寫入過資料的記憶體,包括所有堆區的物件、影象解碼緩衝區,同時,類似Clean memory,也包括App所用到的frameworks。每個framework都會有_DATA段和_DATA_DIRTY段,它們的記憶體是Dirty的。
值得注意的是,在使用framework的過程中會產生Dirty Memory,使用單例或者全域性初始化方法是減少Dirty Memory不錯的方法,因為單例一旦建立就不會銷燬,全域性初始化方法會在類載入時執行。
Compressed Memory
由於快閃記憶體容量和讀寫壽命的限制,iOS 上沒有Disk swap機制,取而代之使用Compressed memory。
Compressed memory是在記憶體緊張時能夠將最近使用過的記憶體佔用壓縮至原有大小的一半以下,並且能夠在需要時解壓複用。它在節省記憶體的同時提高了系統的響應速度,特點總結起來如下:
* Shrinks memory usage 減少了不活躍記憶體佔用
* Improves power efficiency 改善電源效率,通過壓縮減少磁碟IO帶來的損耗
* Minimizes CPU usage 壓縮/解壓十分迅速,能夠儘可能減少 CPU 的時間開銷
* Is multicore aware 支援多核操作
例如,當我們使用Dictionary去快取資料的時候,假設現在已經使用了3頁記憶體,當不訪問的時候可能會被壓縮為1頁,再次使用到時候又會解壓成3頁。
Memory Warning
記憶體警告,不一定總是應用自身導致的
記憶體壓縮技術使得釋放記憶體變得複雜
快取策略
小結
通常情況下,我們所說的記憶體佔用是指Dirty Memory和Compressed Memory,Clean Memory不需要過多關心。
App 能使用比較多的記憶體空間,但是上限會根據裝置不同而不同。Extension能使用的最大記憶體則要低很多,所以當你在開發Extension的時候尤其要注意記憶體使用。當使用的記憶體超出限制的時候,系統會丟擲EXC_RESOURCE_EXCEPTION異常。
Xcode Memory Gauge
在Xcode中,你可以通過Memory Gauge工具,很方便快速的檢視App執行時的記憶體情況,包括記憶體最高佔用、最低佔用,以及在所有程式中的佔用比例等。如果想要檢視更詳細的資料,就需要用到Instruments了。
Instruments
在 Instruments 中,你可以使用Allocations、Leaks、VM Tracker和 Virtual Memory Trace對App進行多維度分析。
Allocations
Leaks
VM Tracker
Virtual Memory Trace
Debug Debugger - Memory Resource Exceptions
當你使用 Xcode 10以前的版本進行除錯時,在記憶體過大時,debug session會直接終止,並且在控制檯列印出異常。從Xcode 10開始,debugger會自動捕獲EXC_RESOURCE RESOURCE_TYPE_MEMORY異常,並斷點在觸發異常丟擲的地方,十分方便定位問題。
Xcode Memory Debugger
Xcode Memory Debugger的記憶體偵錯程式是在Xcode 8中提供的,它可以幫助您跟蹤物件依賴性,週期和洩漏。在Xcode 10中,優化了介面佈局。
你也可以點選File->Export Memory Graph將其匯出為memgraph檔案,通過命令列對其進行分析。下面說下幾個命令列工具
vmmap
vmmap 能夠列印出程式資訊,所有分配給該程式的 VM區域以及VM區域的種類、記憶體佔用資訊等內容。
利用--summary則能夠根據不同的區域型別列印出詳細的記憶體佔用型別和資訊。這裡需要注意的是 SWAPPED SIZE在iOS上指的是Compressed memory size且其值表示壓縮前的佔用大小。
vmmap--summaryApp.memgraph
1
如果您希望檢視更多的資訊,那麼直接呼叫即可。您將獲得所有區域的內容。
vmmap App.memgraph
1
配合管道命令檢視所有動態庫的Ditry Pages的總和
vmmap -pages xxx.memgraph | grep '.dylib' | awk '{sum += $6}END{ print"Total Dirty Pages:"sum}'
1
更多使用方式請檢視vmmap的文件
man vmmap
1
Leak
顧名思義,就是檢視記憶體洩漏的。
leaks xx.memgraph
1
更多使用方式也可以檢視man手冊。
heap
檢視堆區記憶體
heap xx.memgraph
1
預設情況下,是按照數量排序的,當然也可以通過引數-sortBySize讓其來按照大小排序。
heap xx.memgraph-sortBySize
1
排列之後,我們發現了一些巨大的NSConcreteData物件,通過下面的命令,就可以得到每個物件的記憶體地址。
heap xx.memgraph -addresses'NSConcreteData'#得到全部物件的記憶體地址#heap xx.memgraph -addresses all
1
2
3
4
有了這些地址呢,我們就可以知道他們是從哪裡來的。有了這些物件的記憶體地址之後,我們還需要另一樣工具幫助我們做下一步分析。
Enabling Malloc Stack Logging
在Product->Scheme->Edit Scheme->Diagnostics中,開啟 Malloc Stack 功能,建議使用Live Allocations Only選項。
之後lldb會記錄除錯過程中物件建立的堆疊,配合malloc_history工具,就可以定位到那些佔用了過大記憶體的物件是哪裡建立的。
malloc_history
檢視記憶體分配的歷史,使用方法如下
malloc_historyxx.memgraph[address]malloc_historyxx.memgraph--fullStacks[address]
1
2
3
工具的選擇
以上講了很多工具,當遇到記憶體問題時,那我們要如何進行選擇呢?
這裡有三種方法來考慮。您是否想檢視物件的建立?您是否想檢視記憶體中物件的引用或者地址內容?或者您是否想檢視一個例項有多大?
可以根據上圖所示,按照不同情況,來使用不同的工具。
圖片所佔記憶體的大小與圖片的尺寸有關,而不是圖片的檔案大小。
舉個例子,我們這裡有一張590KB圖片,而它的解析度是2048px * 1536px。它實際使用的記憶體不是590KB,而是2048 * 1536 * 4 = 12 MB。
圖片為什麼會佔這麼多的記憶體?這還要從圖片在iOS上顯示的原理說起。一張圖片檔案從磁碟到展示需要經過三步:
載入
解壓縮
渲染
更多關於影象以及如何優化影象的資訊,請檢視WWDC 2018 Image and Graphics Best Practices,也可以直接閱讀前幾天我們小夥伴釋出的文章影象和圖形的最佳實踐)。
影象渲染格式
sRGB格式
Wide格式
亮度和alpha 8格式
alpha 8格式
選擇正確的圖片格式
簡單的回答是:不需要你來選擇格式,而是應該讓格式選擇你。
用UIGraphicsImageRenderer代替UIGraphicsBeginImageContextWithOptions
使用UIGraphicsBeginImageContextWithOptions生成的圖片,每個畫素需要4個位元組表示。建議使用UIGraphicsImageRenderer,這個方法是從iOS 10引入,在iOS 12上會自動選擇最佳的影象格式,可以減少很多記憶體。UIGraphicsImageRenderer可以建立UIImage物件或者進行JPEG/PNG格式的編碼。
此外,如果想修改顏色,可以直接修改tintColor,不會有額外的記憶體開銷。
下采樣
當你縮小一幅影象的時候,會按照取平均值的辦法把多個畫素點變成一個畫素點,這個過程稱為下采樣(Downsampling)。
UIImage在設定和調整大小的時候,需要將原始影象加壓到記憶體中,然後對內部座標空間做一系列轉換,整個過程會消耗很多資源。我們可以使用ImageIO,它可以直接讀取影象大小和後設資料資訊,不會帶來額外的記憶體開銷。
這樣處理,不但記憶體佔用的更低了,而且執行速度也快了50%左右。
假設在 App 裡展示了一張很大圖片,當我們切換到後臺去做其它的操作時,這個圖片還在佔用記憶體。我們應該考慮在合適的時機去回收這類佔用過大的資料。
監聽UIApplicationWillEnterForeground和UIApplicationDidEnterBackground通知
viewWillAppear和viewDidDisappear方法
略過,基本上就是用上面說的命令去除錯一個問題及優化方案去除錯圖片的記憶體問題
記憶體是一個有限的共享資源,要學會使用Xcode分析記憶體工具,從而瞭解應用程式記憶體佔用情況,並使用一些縮減應用程式記憶體佔用空間的技巧和竅門。
相關文章
- WWDC 2018:iOS 記憶體深入研究iOS記憶體
- 探究 iOS 記憶體問題iOS記憶體
- iOS Target-Action模式下記憶體洩露問題深入探究iOS模式記憶體洩露
- WWDC筆記-記憶體策略筆記記憶體
- iOS記憶體深入探索之VM TrackeriOS記憶體
- iOS底層原理探究- NSObject 所佔記憶體iOSObject記憶體
- 深入探究JVM之記憶體結構及字串常量池JVM記憶體字串
- java陣列記憶體的探究Java陣列記憶體
- iOS 深入探究 AutoreleasePooliOS
- iOS底層學習 - 記憶體管理之weak原理探究iOS記憶體
- WWDC2016 Session筆記 - iOS 10 推送Notification新特性Session筆記iOS
- WWDC 2018:iOS 12 通知的新特性iOS
- Swift列舉關聯值的記憶體探究Swift記憶體
- iOS 記憶體管理iOS記憶體
- iOS 中的記憶體管理iOS記憶體
- 理解 iOS 的記憶體管理iOS記憶體
- iOS-效能優化深入探究iOS優化
- 深入理解Java的堆記憶體和執行緒記憶體Java記憶體執行緒
- 初步探究Android記憶體洩漏(1)Android記憶體
- “理解”iOS記憶體管理iOS記憶體
- iOS 記憶體管理MRCiOS記憶體
- iOS 記憶體管理研究iOS記憶體
- 探索iOS記憶體分配iOS記憶體
- iOS記憶體淺談iOS記憶體
- iOS arc 記憶體管理iOS記憶體
- iOS另類的記憶體管理iOS記憶體
- iOS 進階—— iOS 記憶體管理 & BlockiOS記憶體BloC
- Python深入 Python的記憶體管理Python記憶體
- 深入淺出記憶體馬(一)記憶體
- 深入淺出Java記憶體模型Java記憶體模型
- Redis記憶體碎片深入分析Redis記憶體
- 【記憶體洩漏和記憶體溢位】JavaScript之深入淺出理解記憶體洩漏和記憶體溢位記憶體溢位JavaScript
- 理解 iOS 和 macOS 的記憶體管理iOSMac記憶體
- iOS微信記憶體監控iOS記憶體
- iOS記憶體管理詳解iOS記憶體
- Weex-iOS 記憶體分析iOS記憶體
- iOS記憶體管理相關iOS記憶體
- iOS記憶體管理淺析iOS記憶體