iOS 內存機制特點
有限的可用內存
iPhone 設備的 RAM 一直非常緊缺,iPhone 一代只有 128MB,直到 iPhone5 時達到了 1GB,並且在 iPhone7 plus 達到了 3GB。StackOverFlow 上提供了部分 iPhone 機型的可用內存數目。
低內存通知
在可用物理內存較少時,iOS 會給各應用發出低內存廣播通知,如果此後可用內存仍然低於特定值,則會殺死優先級較低的進程。
沒有內存交換機制
桌面操作系統可以在物理內存緊張的時候把暫時不用的物理內存置換到磁盤上,並在需要的時候再次加載到內存中。而 iOS 沒有這種機制,原因是移動設備的閃存沒有 PC 機那麼大的硬盤,而且頻繁的讀寫閃存會降低其壽命。目前 iOS 在內存不足時采用的方案是殺死優先級較低的進程。
使用虛擬內存機制
和大多數桌面操作系統一樣,iOS 也使用虛擬內存機制。
虛擬內存
關於虛擬內存的原理和優缺點就不再累述,這裡說下 iOS 虛擬內存機制中與眾不同的地方。
內存分頁
iOS 把虛擬內存每 4KB 劃分成一個 Page,並不是所有的 Page 都會映射到物理內存中。每個 Page 有三種狀態:
Nonresident
是否 Resident 是一個 Page 狀態的重要標識,如果 Page 被映射到內存裡了,這個 Page 就是 Resident 狀態,否則就是 Nonresident 狀態;
Resident and clean
基於 readonly 文件而被加載到內存中的 Page 稱為 clean memory,比如:系統 framework、可執行文件、通過 mmap 方式讀取的文件 等。這種 Page 由於是加載自不可變的文件,因此可以在物理內存緊張時被 iOS 自動 unload 出去,並且在需要的時候再重新從原來的文件加載到內存中。
Resident and dirty
凡是非 clean 的 Page 都是 dirty 的,它們的共同特點是 Page 在閃存中沒有對應的文件,比如通過 alloc 在堆上創建的內存空間,已經解壓的圖片,database caches 等。dirty memory 不能被操作系統交換出去,只有在進程被殺死的時候才能被回收,因此在系統發生內存告警時,如果進程創建了大量的 dirty memory,那麼將很有可能被 kill 掉。
舉例說明
Malloc 分配內存
如前問所述,Malloc 的內存都是 Resident dirty 的,但事實上並非如此,比如:
char *p = valloc(2 * 4096);
此時會在虛擬內存裡申請兩份 4096 字節的內存,但由於申請後沒有使用,操作系統不會真正為剛申請的內存空間分配對應的物理內存空間,因此此時該內存空間處於 Nonresident 狀態。如果對 p[0] 賦值:
p[0] = 1;
此時 P[0] 會被加載到物理內存上,由此變成 Resident dirty 狀態,同理如果對 p[1] 賦值也一樣。
mmap 加載文件
一個文件通過如果下述 mmap 方式加載:
NSData *data = [NSData dataWithContentsOfMappedFile:file];char *p = (char *)[data bytes];
此時文件由於未被使用,因此也僅僅是在虛擬內存中,操作系統並沒有將其映射到物理裡,因此所屬 Page 的狀態是 Nonresident。如果調用以下代碼:
printf("%c", p[0]);
此時由於該文件的 p[0] 部分被使用,操作系統就會將 p[0] 部分加載到物理內存中,又因為 p 對應的存儲區域是一個 mmap 方式加載的只讀文件,因此 p[0] 對應的 Page 就是 Resident clean 的,而 p[1] 往後的部分由於仍然未被使用,Page 的狀態不變。
需要做什麼
對於開發者來說,要想減少應用因內存告警被系統殺掉,應做到以下幾點:
該盡可能減少 dirty 內存的創建
要盡量保證 dirty 內存用完之後及時釋放
及時處理系統內存告警通知,釋放掉大量占用內存並且可重建的對象
在發生內存告警時,不再持續申請內存,更不能申請較大塊的內存
參考文檔
List of iOS devices
WWDC2010 Session 417:Advanced Performance Optimization on iPhone OS, Part 2
WWDC2012 Session 242:iOS App Performance: Memory