說說iOS與記憶體管理(中)

發表於2015-09-28

接著上文簡單整理下iOS常見記憶體問題及排查相關的工具和方法。

上篇日誌是6月份寫的,由於工作的內容比較豐富,這麼久一直沒能更新blog,有點小遺憾,這是到這是2015年8月最後一天,趁著8月還沒過,趕快把這篇補上。

0. 記憶體工具

針對iOS開發,我們所能使用的記憶體排查工具選擇其實並不算特別多。最主要的除錯工具就是Instruments。然而,如果仔細探查細節,Instruments還是整合了很多不錯的除錯模板/Library的。

本文針對如下幾類應用場景,對通用的除錯方法做基本介紹:

  • 最基本最常用的記憶體問題場景——記憶體洩露、過度釋放
  • malloc相關的堆記憶體分配問題排查相關工具
  • 其它記憶體工具

1. 記憶體洩露與過度釋放

我們應該都知道,iOS開發過程中,使用Objective-C分配的堆記憶體都是通過引用計數來做保留和釋放的。一塊記憶體初始分配,引用計數為1,此後每新增一個強引用,引用計數增加1;釋放正好相反,每一次release,引用計數減1,直到為0,物件所用記憶體被真正free掉,以被再次複用。然而,實際開發當中,總有一些原因導致引用計數無法按正常邏輯減少到0,或者減少到0之後仍然被呼叫release,前者是記憶體洩露,後者則是過度釋放。

當記憶體洩露發生時,執行的App不會直接第發生明顯問題,但廢棄記憶體得不到回收,在長時間持續執行後,App程式會由於可用記憶體不斷變低而被kill或帶來其它隱患。

避免記憶體洩露,首要是有良好的程式碼習慣,避免迴圈引用、會用weak,其次可以通過Analyze來進行靜態程式碼檢查,以發現在語法上顯而易見的記憶體洩露問題。但更多時候,記憶體洩露是執行時的問題,這時可用Instruments中的Allocation和Leaks來不斷重複操作App,發現和定位記憶體洩露點。

當執行時發生顯示記憶體洩露時,Leaks會在時間軸上標出紅色指示線,同時在Instruments的下方會列出呼叫細節,結合系統提供的malloc歷史,其中包含引用計數變化情況,以及呼叫棧可以很直接地找到洩露原因。

同時對於一些“隱式”的情況,需要反覆操作,同時觀察Allocation中只增不減,一直建立新物件而不釋放老物件的情況。

過度釋放,是對同一個物件釋放了過多的次數,其實當引用計數降到0時,物件佔用的記憶體已經被釋放掉,此時指向原物件的指標就成了“懸垂指標”,如若再對其進行任何方法的呼叫,(原則上)都會直接crash(然而由於某些特殊的情況,不會馬上crash)。

對於這種問題,可以直接使用Zombie,當過度釋放發生時會立即停在發生問題的位置,同時結合記憶體分配釋放歷史和呼叫棧,可以發現問題。

至於上文提到的不會crash的原因,其實有很多,比如:

  • 物件記憶體釋放時,所用記憶體並沒有完全被擦除,仍有舊物件部分資料可用
  • 原記憶體位置被寫入同類或同樣結構的資料

2. malloc庫提供的相關工具

上一段提到,物件釋放時,所用記憶體並沒有完全被擦除,仍有舊物件部分資料可用,如果不使用Zombie除錯,App可能不會直接crash。對付這種情況,其實很簡單,可以在物件記憶體釋放時寫入無意義資料,如0×55,0xaa等,而系統已經幫我們做了這個工具,那就是Scribble,在Xcode的Edit Scheme裡,Diagnostics Tab下勾選Enable Scribble。

Scribble其實是malloc庫(libsystem_malloc.dylib)自身提供的除錯方案,除了Scribble,malloc還提供了很多其它的除錯工具/方案,在Diagnostics Tab下你應該都看到了。其實,malloc可用的工具還不止這些,通過環境變數至少還可以新增如下除錯引數:

  • MallocLogFile
  • MallocGuardEdges
  • MallocDoNotProtectPrelude
  • MallocDoNotProtectPostlude
  • StackLogging
  • StackLoggingNoCompact
  • MallocCorruptionAbort
  • MallocNanoZone
  • MallocCheckHeap

其中,有幾個引數是和記錄分配歷史的日誌有關的。

除此之外,Xcode裡還提供了Guard malloc,這個是等同於預設malloc庫功能的另外一個除錯庫,為了定位大記憶體越界訪問問題,不過只能在模擬器上使用(個人分析是因為真機根本承受不起保護頁記憶體消耗)。

對以上引數的實現細節感興趣的,可以參看蘋果開放出來的malloc原始碼。

3. 其它

下面我還想對Instruments裡的三樣東西做簡要介紹:

  • Allocation
  • Activity Monitor
  • VM Tracker

Allocation針對堆記憶體及匿名對映的情況提供了詳細的資料,包括某類物件有多少個,物件的具體地址,佔用多大記憶體等。通過Allocation,可以對我們開發中實際接觸到的記憶體有非常全面的把握。

Activity Monitor則從系統的層面,對主要程式的CPU、記憶體、網路等資料做分析。從記憶體方面看,我們可以知道佔用系統記憶體最大的5個App,它提供的資料包含了實際實體記憶體和虛擬記憶體部分。

VM Tracker則可以告訴我們哪些部分是Dirty的,Dirty的資料系統不會直接清理掉,因為這些資料是被寫過的,無法通過外部儲存直接生成,只能維持在記憶體當中。此外,通過VM Tracker我們還可以看到Region Map,看到程式地址空間各部分的對映情況。

除了上面提到的這些,對於詭異的記憶體問題,我們可以對Xcode7中的Address Sanitizer期待和試用下,也許它真得能幫我們解決好多問題!

相關文章