iOS記憶體淺談

zhYx__發表於2017-10-09

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 的,但事實上並非如此,比如:

1
char *p = valloc(2 * 4096);

此時會在虛擬記憶體裡申請兩份 4096 位元組的記憶體,但由於申請後沒有使用,作業系統不會真正為剛申請的記憶體空間分配對應的實體記憶體空間,因此此時該記憶體空間處於 Nonresident 狀態。如果對 p[0] 賦值:

1
p[0] = 1;

此時 P[0] 會被載入到實體記憶體上,由此變成 Resident dirty 狀態,同理如果對 p[1] 賦值也一樣。

  • mmap 載入檔案

一個檔案通過如果下述 mmap 方式載入:

1
NSData *data = [NSData dataWithContentsOfMappedFile:file];char *p = (char *)[data bytes];

此時檔案由於未被使用,因此也僅僅是在虛擬記憶體中,作業系統並沒有將其對映到物理裡,因此所屬 Page 的狀態是 Nonresident。如果呼叫以下程式碼:

1
printf("%c", p[0]);

此時由於該檔案的 p[0] 部分被使用,作業系統就會將 p[0] 部分載入到實體記憶體中,又因為 p 對應的儲存區域是一個 mmap 方式載入的只讀檔案,因此 p[0] 對應的 Page 就是 Resident clean 的,而 p[1] 往後的部分由於仍然未被使用,Page 的狀態不變。

需要做什麼

對於開發者來說,要想減少應用因記憶體告警被系統殺掉,應做到以下幾點:

  • 該儘可能減少 dirty 記憶體的建立

  • 要儘量保證 dirty 記憶體用完之後及時釋放

  • 及時處理系統記憶體告警通知,釋放掉大量佔用記憶體並且可重建的物件

  • 在發生記憶體告警時,不再持續申請記憶體,更不能申請較大塊的記憶體

相關文章