前言
我們在查看iOS應用內存時,最常見的手法就是查看左邊的Debug Navigator。不知你是否也曾困惑於這個內存究竟包括哪些部分,或者使用Allocations模版觀察內存時發現無法和Debug Navigator顯示的內存匹配上,這篇文章將帶你解答這些疑惑。
Debug Navigator VS Allocations
我們運行一個很簡單的iOS App,我只在ViewController中放置了一個View,然後對比下Debug Navigator 和 Allocations給出的內存用量。
可以發現,Debug Navigator給出的是79.3M,而Allocations統計的所有堆和相關VM加起來才38.72M,相差的還是很多的。在之前的文章中我有介紹關於Allocations和VM Tracker的深入理解,其實Allocations中主要包含的是所有MALLOC_XXX VM Region和部分App進程創建的VM Region。非動態的內存,以及部分其他動態庫創建的VM Region並不在Allocations的統計範圍內。比如主程序或者動態庫的_DATA數據段,這些數據內存區域並非通過malloc分配,也就沒有統計在All Heap Allocations中,所以你會發現All Heap Allocations往往會比較小。除非你自行使用malloc系列方法創建大內存塊,否則很難看到All Heap Allocations有一個大的數值。我們在實際的App中,大的內存佔用一般都是類似於WebKit,ImageIO,CoreAnimation等虛擬內存區域(VM Region),這些VM Region一般由系統代碼生成和管理,我們編寫的代碼如果間接引用了這些內存而沒有釋放,也就會造成大面積的內存洩漏。
Debug Navigator VS VM Tracker
接下來我們來看看VM Tracker統計的內存如何,下面是截圖。
在看VM Tracker時,我們主要看Dirty Size和Swapped Size,由於我是在模擬器上調試的,所以才需要關注Swapped Size,在手機上,主進程的內存應該是不會交換到硬盤上的,內存不足時,會觸發內存警告。Dirty Size主要指的是不可被重新載入的內存區域大小,比如函數棧,如果你把函數棧的數據給抹掉了,也就無法恢復之前的函數調用棧數據了,這種可以稱為Dirty內存區域,但如果是通過文件內存映射載入到內存區域的,可以先清除掉這部分內存裡的數據暫時把這部分內存給別人用,需要時再從文件載入到內存,這種內存區域可以認為是非Dirty的。Dirty Size可以代表一個進程需求的最少內存量,當然在模擬器上,還要加上被交換出去的數據大小,即Swapped Size。
我們回到上圖,VM Tracker給出的Dirty Size總量時69.79M,還是和79.3M有些差距。不過我們可在在圖中看到_DATA數據段,Stack(函數棧)等等Allocations沒有統計的內存區域。
Debug Navigator VS vmmap command line
蘋果除了Instrument的VM Tracker可以查看虛擬內存之外,還有一個vmmap命令行可以查看進程的虛擬內存分配。使用模擬器啟動App,通過Activity Monitor找到App的進程ID,比如1364,使用vmmap查看它的虛擬內存分配。
vmmap 1364
結果如下
... VIRTUAL RESIDENT DIRTY SWAPPED VOLATILE NONVOL EMPTY REGION REGION TYPE SIZE SIZE SIZE SIZE SIZE SIZE SIZE COUNT (non-coalesced) =========== ======= ======== ===== ======= ======== ====== ===== ======= Activity Tracing 256K 36K 36K 12K 0K 36K 0K 2 CoreAnimation 36.1M 33.3M 33.3M 2848K 0K 33.3M 0K 2 Kernel Alloc Once 8K 8K 8K 0K 0K 0K 0K 2 MALLOC guard page 48K 0K 0K 0K 0K 0K 0K 13 MALLOC metadata 260K 92K 92K 32K 0K 0K 0K 16 MALLOC_LARGE 4360K 4256K 4256K 104K 0K 0K 0K 3 see MALLOC ZONE table belowMALLOC_LARGE (empty) 3988K 2080K 2080K 1908K 0K 0K 0K 2 see MALLOC ZONE table belowMALLOC_LARGE metadata 4K 4K 4K 0K 0K 0K 0K 2 see MALLOC ZONE table belowMALLOC_NANO 16.0M 2160K 2160K 0K 0K 0K 0K 3 see MALLOC ZONE table belowMALLOC_SMALL 40.0M 840K 840K 356K 0K 0K 0K 3 see MALLOC ZONE table belowMALLOC_TINY 8192K 320K 320K 36K 0K 0K 0K 3 see MALLOC ZONE table belowPerformance tool data 316K 264K 264K 52K 0K 0K 0K 3 not counted in TOTAL belowSTACK GUARD 56.0M 0K 0K 0K 0K 0K 0K 4 Stack 9232K 76K 76K 20K 0K 0K 0K 7 __DATA 35.4M 16.6M 16.4M 12.3M 0K 0K 0K 282 __FONT_DATA 4K 0K 0K 0K 0K 0K 0K 2 __LINKEDIT 96.0M 69.5M 0K 0K 0K 0K 0K 225 __TEXT 228.3M 54.4M 4K 4K 0K 0K 0K 225 __UNICODE 560K 320K 0K 0K 0K 0K 0K 2 mapped file 28.7M 2116K 0K 0K 0K 0K 0K 3 shared memory 44K 24K 24K 8K 0K 0K 0K 5 =========== ======= ======== ===== ======= ======== ====== ===== ======= TOTAL 562.9M 185.8M 59.3M 17.5M 0K 33.4M 0K 786
我們將Dirty Size和Swapped Size總量相加59.3M + 17.5M = 76.8M,和Debug Navigator給的值已經很相近了,我們再看上面的表格,發現有一行是這麼寫的Performance tool data ... not counted in TOTAL below,Performance tool data並沒有統計在最下面的TOTAL中,因為這些數據是Debug時提供調用回溯數據用的,所以vmmap默認認為沒有價值,沒有統計。但是Debug Navigator不這麼認為,我們加上Performance tool data的內存用量,264K + 52K = 316K = 0.308M,加上之前的76.8M就是77.108M,由於本次並沒有使用Instruments進行profile,所以佔用的內存會少一些,Debug Navigator顯示的剛好是77.1M。至於為什麼vmmap顯示的數據要比Instruments VM Tracker的要完整,目前我還沒有明確的答案。
shared memory
最後我要提到的時共享內存,共享內存可以提供跨進程訪問的能力,不過如果你的App使用了別的進程創建的共享內存,那麼Debug Navigator是不會將它計入你自己的內存總量的,不過vmmap會將它加入TOTAL中,所以可能會導致vmmap計算的內存量會大於Debug Navigator統計內存量。由於目前iOS對於shared memory的一些API並不支持,我也沒有深入研究,只是在OSX中驗證了這一點。
總結
最後來總結一下,Debug Navigator其實就是統計了當前進程的所有虛擬內存的Dirty Size + Swapped Size,當然還要剔除掉對第三方共享內存的使用量,當我們發現Debug Navigator的內存量飆高時,不僅僅要去關注Heap上的內存用量,更要關注VM Tracker中那些大Dirty Size的VM Region,這樣才能更透徹的瞭解你的App究竟是怎樣使用內存的。
鏈接:https://www.jianshu.com/p/827996b7aed0
閱讀更多 CocoaChina 的文章