簡單總結下iOS內存方面的一些知識html
磁盤內部有一個區域叫作交換空間(Swap Space),MMU(內存管理單元) 會將暫時不用的內存塊內容寫在交互空間上(硬盤),這就是Swap Out;當須要時候再從Swap Space中讀取到內存中,這就是Swap In;Swap in和swap out的操做都是比較耗時的, 頻繁的Swap in和Swap out操做很是影響系統性能;python
Page In/Out和 Swap In/Out 概念相似,只不過Page In/Out是將某些頁的數據寫到內存/從內存寫回磁盤交互區;而Swap In/Out是將整個地址空間的數據寫到內存/從內存寫回磁盤交互區;本質都是交互機制。git
macOS支持這類交換機制,可是iOS不支持;主要有兩方面考慮吧:程序員
從高地址到低地址各區域以下:github
內存頁按照各自的分配和使用狀態,分爲 Clean
和 Dirty
兩類。其中Clean Page
是能夠被回收的,Dirty Page
不能;web
int *array = malloc(20000 * sizeof(int)); // 第1步
array[0] = 32 // 第2步
array[19999] = 64 // 第3步
複製代碼
第一步,申請一塊長度爲80000 字節的內存空間,按照一頁 16KB 來計算,就須要 6 頁內存來存儲。當這些內存頁開闢出來的時候,它們都是 Clean 的;objective-c
第二步,向處於第一頁的內存寫入數據時,第一頁內存會變成 Dirty;緩存
第三步,當向處於最後一頁的內存寫入數據時,這一頁也會變成 Dirty;bash
iOS進程中全部內存就是由許許多多的VM Region
組成的;服務器
VM Region
指一段連續的內存頁(在虛擬地址空間裏),VM Region的結構以下
struct vm_region_submap_info_64 {
vm_prot_t protection; /* present access protection */
vm_prot_t max_protection; /* max avail through vm_prot */
vm_inherit_t inheritance;/* behavior of map/obj on fork */
memory_object_offset_t offset; /* offset into object/map */
unsigned int user_tag; /* user tag on map entry */
unsigned int pages_resident; /* only valid for objects */
unsigned int pages_shared_now_private; /* only for objects */
unsigned int pages_swapped_out; /* only for objects */
unsigned int pages_dirtied; /* only for objects */
unsigned int ref_count; /* obj/map mappers, etc */
unsigned short shadow_depth; /* only for obj */
unsigned char external_pager; /* only for obj */
unsigned char share_mode; /* see enumeration */
boolean_t is_submap; /* submap vs obj */
vm_behavior_t behavior; /* access behavior hint */
vm_offset_t object_id; /* obj/map name, not a handle */
unsigned short user_wired_count;
};
複製代碼
VM Region包含的重要信息有:
能夠經過了解pages_dirtied和pages_swapped_out來了解VM Region
的真實物理內存使用。
從 iOS7 開始,系統開始採用Compressed Memory
機制優化內存使用,內存類型能夠分爲三類:
Clean Memory
:能夠被釋放或重建的,主要包括:
_DATA_CONST
段,當 App 在運行時使用到了某個 framework,它所對應的 _DATA_CONST
的內存就會由 Clean 變爲 Dirty。Dirty Memory
:指那些被寫入過數據的內存,主要包括:
_DATA
段和 _DATA_DIRTY
段)在使用 framework 的過程當中會產生Dirty Memory,使用單例或者全局初始化方法是減小Dirty Memory;這是由於單例一旦建立就不會銷燬,全局初始化方法會在 class 加載時執行。
複製代碼
Compressed Memory
:
在內存吃緊時,系統會將不使用的內存進行壓縮(Compresses unaccessed pages)
在須要的時候,進行解壓 (Decompresses pages upon access)
優點:減小了不活躍內存佔用;減小磁盤IO帶來的損耗;壓縮/解壓十分迅速,可以儘量減小 CPU 的時間開銷;支持多核操做。
舉例:當咱們使用 NSDictionary
去緩存數據的時候,假設如今已經使用了 3 頁內存,當不訪問的時候可能會被壓縮爲 1 頁,再次使用到時候又會解壓成 3 頁。
介紹Clear Memory
和 Dirty Memory
的Code以下:
//堆分配的內存 Dirty Memory
NSString *str1 = [NSString stringWithString:@"Welcome!"];
//常量字符串, 存放在一個只讀數據段裏面,這段內存釋放後,還能夠在讀取重建 Clear Memory
NSString *str2 = @"Welcome!";
//分配100M虛擬內存,當沒有用時沒有創建映射,Clear Memory
char *buf = malloc(100 * 1024 *1024); 關係
for (int i = 0; i < 3 * 1024 * 1024; ++i) {
//寫入數據了,Dirty Memory
buf[i] = rand();
}
複製代碼
說明:在內存吃緊的狀況下,釋放Clean Memory
,不能釋放Dirty Memory
,因此Dirty Memory
的內存越多,App的穩定性越差。
Jetsam
機制Jetsam機制是操做系統爲了控制內存資源過分使用而採用的一種管理機制;Jetsam是一個獨立運行的進程,會把一些優先級不高或者佔用內存過大的App殺掉;在殺掉App後會記錄一些數據信息並保存到日誌。
App優先級能夠這麼簡單理解:前臺App > 後臺App; 佔用內存少 > 佔用內存多;
Jetsam產生的這些日誌能夠在手機設置->隱私->分析中找到,日誌是以JetsamEvent
開頭,日誌中有內存頁大小(pageSize),CPU時間(cpuTime)等字段。
查看設置->隱私->分析
中以JetsamEvent開頭的系統日誌,關注兩個重要的信息;
"pageSize" : 16384,
//內存頁達到上限
"rpages" : 948, //App 佔用的內存頁數量
"reason" : "per-process-limit", //App 佔用的內存超過了系統對單個 App 的內存限制。
複製代碼
applicationDidReceiveMemoryWarning:
didReceiveMemoryWarning
FOOM(Foreground Out Of Memory),是指App在前臺因消耗內存過多引發系統強殺。對用戶而言,表現跟Crash同樣。
Facebook早在2015年8月提出FOOM檢測辦法,大體原理是排除各類狀況後,剩餘的狀況是FOOM;Facebook如何斷定上一次啓動是否出現FOOM方法:
排查法有誤報的可能,由於有些被系統強殺case,可是咱們捕獲不到信息,也可能被歸類到OOM;已知被系統強殺的case是:OOM和watchdog(Code 0x8badf00d)。
發現FOOM問題的關鍵:監控App使用內存增加,在收到內存警告通知時,dump 內存信息,獲取對象名稱、對象個數、各對象的內存值,並在合適的時機上報到服務器;增強對大內存的分配監控。
內存泄露(Memory Leak):指申請的內存空間使用完畢以後未回收,內存泄露問題多的話,對App質量影響很大;
目前引發內存泄露的主要緣由是循環引用(堆內存中對象相互引用,彼此都得不到釋放的機會),目前,調試階段使用Instrument的Leaks工具發現,線上利用MLeaksFinder發現後上報;
如何避免內存泄露在後面有介紹
WKWebview白屏問題,嚴格來講,是一種內存方面的問題;以前的UIWebview由於內存使用過大會Crash,而WKWebview不會Crash,會白屏;
WKWebView是一個多進程組件,Network Loading
以及UI Rendering
在其它進程中執行,當WKWebView整體的內存佔用比較大時,WebContent Process
會Crash,從而出現白屏現象。
解決辦法:
KVO監聽URL, 當URL爲nil,從新reload
在進程被終止回調中,從新reload
// 此方法適用iOS9.0以上
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0){
//reload
}
複製代碼
野指針:指向一個已刪除的對象 或 受限內存區域的指針;目前此類問題不多了,主要來自兩個方面
assign(unsafe_unretain)
修飾的 delegate
和target-action
修改爲了weak,內存被回收的時候,這些指針設爲nil,這也大幅度減小了野指針的出現。目前絕大部分App都是iOS 9起步,野指針少了不少,可是工程中依然會有野指針問題,本質仍是內存使用不當;
Mach Exception
大多數都是野指針的問題,崩潰日誌裏最常見objc_msgSend
和unrecognized selector sent to
等等。
對於野指針問題,最好能復現,使用Zombie Object
幫助調試,Zombie Object
實現原理就是 hook 住了對象的dealloc
方法,經過調用本身的__dealloc_zombie
方法來把對象進行殭屍化,當這個對象再次收到消息,objc_msgsend
的時候,調用abort()崩潰並打印出調用的方法。。
在iOS 10.0 - 10.1,蘋果bug引入nano_free Crash問題,這些Crash發生libsystem_malloc.dylib
中的 nano zone
內的;
libsystem_malloc.dylib
中,對內存的管理有兩個實現:nano zone
和scalable zone
。他們分別管理不一樣大小的內存塊:
當時微信團隊提出的幾種解決思路,最後給的解決方案是不使用nano zone,具體描述
guard zone
。malloc_zone_create
建立的)。
更多見:
善待"大"圖(位圖大小大於60MB)解碼:將原圖裁剪成多個小圖,而後依次繪製到目標位圖context中,具體可見SDWebImage中和關於**decodedAndScaledDownImageWithImage:**的實現;
限制併發解碼圖片的個數;
UIGraphicsImageRenderer
建立 image 上下文,而不是UIGraphicsBeginImageContextWithOptions
,由於前者的性能更好、更高效,而且支持廣色域;合理使用autorealsepool
,下降內存峯值,避免 OOM
複用大內存對象,如UITableViewCell對象;懶加載大的內存對象
imageNamed 和 imageWithContentOfFile 的選擇
建議NSData讀取文件方式
[NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:&error]
;用 NSCache
代替 NSMutableDictionary
, 由於 NSCache
能夠自動清理內存,在內存吃緊的時候會更加合理。
使用 NSPurgableData 代替NSData,主要緣由以下:
棧內存分配:alloca(size_t)
堆內存分配:calloc VS malloc + memset
calloc(size_t num,size_t size)
分配內存時是虛擬內存,只有在訪問的時候纔會發生物理頁的映射關係;malloc + memset
會產生Dirty Memory
calloc
函數獲得的內存空間是通過初始化的,其內容全爲0,而malloc
函數獲得的內存空間是未初始化的,必須使用memset函數來初始化;weak strong dance
來解決 block 中的循環引用問題;以前對內存問題一些整理
以前對圖片解碼和圖片優化一些總結