在運行iOS(OSX)程序時,左側的Debug Navigator
中能夠看見當前使用的內存。咱們也可使用Instruments的Allocations
模板來追蹤對象的建立和釋放。不知道你是否也曾困惑於Debug Navigator
顯示的內存和Allocations
顯示的總內存對不上號的問題。本篇文章將帶你深刻了解iOS的內存分配。node
在Instruments的Allocations
模板中,能夠看到主要統計的是All Heap & Anonymous VM
的內存使用量。All Heap
好理解,就是App運行過程當中在堆上分配的內存。咱們能夠經過搜索關鍵字查看你關注的類在堆上的內存分配狀況。那麼Anonymous VM
是什麼呢?按照官方描述,它是和你的App進程關聯比較大的VM regions。原文以下。數組
interesting VM regions such as graphics- and Core Data-related. Hides mapped files, dylibs, and some large reserved VM regions.
複製代碼
什麼是VM Regions呢?要知道這個首先要了解什麼是虛擬內存。當咱們向系統申請內存時,系統並不會給你返回物理內存的地址,而是給你一個虛擬內存地址。每一個進程都擁有相同大小的虛擬地址空間,對於32位的進程,能夠擁有4GB的虛擬內存,64位進程則更多,可達18EB。只有咱們開始使用申請到的虛擬內存時,系統纔會將虛擬地址映射到物理地址上,從而讓程序使用真實的物理內存。下面是一個示意圖,我簡化了概念。 bash
進程A和B都擁有1到4的虛擬內存。系統經過虛擬內存到物理內存的映射,讓A和B均可以使用到物理內存。上圖中物理內存是充足的,可是若是A佔用了大部份內存,B想要使用物理內存的時候物理內存卻不夠該怎麼辦呢?在OSX上系統會將不活躍的內存塊寫入硬盤,通常稱之爲swapping out
。iOS上則會通知App,讓App清理內存,也就是咱們熟知的Memory Warning。markdown
系統會對虛擬內存和物理內存進行分頁,虛擬內存到物理內存的映射都是以頁爲最小粒度的。在OSX和早期的iOS系統中,物理和虛擬內存都按照4KB的大小進行分頁。iOS近期的系統中,基於A7和A8處理器的系統,物理內存按照4KB分頁,虛擬內存按照16KB分頁。基於A9處理器的系統,物理和虛擬內存都是以16KB進行分頁。系統將內存頁分爲三種狀態。app
當可用的內存頁下降到必定的閥值時,系統就會採起低內存應對措施,在OSX中,系統會將非活躍內存頁交換到硬盤上,而在iOS中,則會觸發Memory Warning,若是你的App沒有處理低內存警告而且還在後臺佔用太多內存,則有可能被殺掉。ide
爲了更好的管理內存頁,系統將一組連續的內存頁關聯到一個VMObject上,VMObject主要包含下面的屬性。函數
Anonymous VM
裏看到的每條記錄都是一個VMObject或者也能夠稱之爲VM Region
。那麼堆和VM Region是什麼關係呢?按照前面的說法,應該任何內存分配都逃不過虛擬內存這套流程,堆應該也是一個VM Region纔對。咱們應該怎樣才能知道堆和VM Region的關係呢?Instruments中有一個VM Track模版,能夠幫助咱們清楚的瞭解他們的關係。我建立了一個空的Command Line Tool App。 工具
使用下面的代碼。測試
int main(int argc, const char * argv[]) { NSMutableSet *objs = [NSMutableSet new]; @autoreleasepool { for (int i = 0; i < 1000; ++i) { CustomObject *obj = [CustomObject new]; [objs addObject:obj]; } sleep(100000); } return 0; } 複製代碼
CustomObject
是一個簡單的OC類,只包含一個long類型的數組屬性。優化
@interface CustomObject() { long a[200]; } @end 複製代碼
運行Profile,選擇Allocation模版,進入後再添加VM Track模版,這裏不知道爲何Allocation模版自帶的VM Track不工做,只能本身手動加一個了。
咱們在All Heap & Anonymous VM
下能夠看到,CustomObject
有1000個實例,點擊CustomObject
右邊的箭頭,查看對象地址。
第一個地址是0x7faab2800000
。咱們切換到最底下的VM Track,將模式調整爲Regions Map。
而後找到Address Range爲0x7faab2800000
開頭的Region,咱們發現這個Region的Type是MALLOC_SMALL。點擊箭頭看詳情,你將會看到這個Region中的內存頁列表。
可能你已經發現了,截圖中的內存頁Swapped列下都是被標記的,由於我測試的是Mac上的App,因此當內存頁不活躍時會被交換到硬盤上。這也就驗證了咱們在上面提到的交換機制。若是咱們將CustomObject
的尺寸變大,好比做以下變更。
@interface CustomObject() { long a[20000]; } @end 複製代碼
內存上會有什麼變化呢?答案是CustomObject
會被移動到MALLOC_LARGE內存區。
因此總的來講,堆區會被劃分紅不少不一樣的VM Region,不一樣類型的內存分配根據需求進入不一樣的VM Region。除了MALLOC_LARGE和MALLOC_SMALL外,還有MALLOC_TINY, MALLOC metadata等等。具體什麼樣的內存分配進什麼樣的VM Region,我本身也還在探索中。
咱們在VM Track中能夠看到,一個VM Region有4種size。
Virtual Size
顧名思義,就是虛擬內存大小,將一個VM Region的結束地址減去起始地址就是這個值。Resident Size
指的是實際使用物理內存的大小。Swapped Size
則是交換到硬盤上的大小,僅OSX可用。Dirty Size
根據官方的解釋個人理解是若是一個內存頁想要被複用,必須將內容寫到硬盤上的話,這個內存頁就是Dirty的。下面是官方對Dirty Size
的解釋。secondary storage
能夠理解爲硬盤。The amount of memory currently being used that must be written to secondary storage before being reused.
複製代碼
因此通常來講app運行過程當中在堆上動態分配的內存頁都是Dirty的,加載動態庫或者文件內存映射產生的內存頁則是非Dirty的。綜上,咱們能夠總結出, Virtual Size >= Resident Size + Swapped Size >= Dirty Size + Swapped Size
,
咱們除了使用NSObject的alloc分配內存外,還可使用c的函數malloc進行內存分配。malloc的內存分配固然也是先分配虛擬內存,而後使用的時候再映射到物理內存,不過malloc有一個缺陷,必須配合memset將內存區中全部的值設置爲0。這樣就致使了一個問題,malloc出一塊內存區域時,系統並無分配物理內存。然而,調用memset後,系統將會把malloc出的全部虛擬內存關聯到物理內存上,由於你訪問了全部內存區域。咱們經過代碼來驗證一下。在main方法中,建立一個1024*1024的內存塊,也就是1M。
void *memBlock = malloc(1024 * 1024);
複製代碼
咱們發現MALLOC_LARGE中有一塊虛擬內存大小爲1M的VM Region。由於咱們沒有使用這塊內存,因此其餘Size都是0。如今咱們加上memset再觀察。
void *memBlock = malloc(1024 * 1024);
memset(memBlock, 0, 1024 * 1024);
複製代碼
如今Resident Size
,Dirty Size
也是1M了,說明這塊內存已經被映射到物理內存中去了。爲了解決這個問題,蘋果官方推薦使用calloc代替malloc,calloc返回的內存區域會自動清零,並且只有使用時纔會關聯到物理內存並清零。
相信你們對NSZone並不陌生,allocWithZone
或者copyWithZone
這2個方法你們應該也常常見到。那麼Zone到底是什麼呢?Zone能夠被理解爲一組內存塊,在某個Zone裏分配的內存塊,會隨着這個Zone的銷燬而銷燬,因此Zone能夠加速大量小內存塊的集體銷燬。不過NSZone實際上已經被蘋果拋棄。你能夠建立本身的NSZone,而後使用allocWithZone
將你的OC對象在這個NSZone上分配,可是你的對象仍是會被分配在默認的NSZone裏。咱們能夠用heap工具查看進程的Zone分佈狀況。首先使用下面的代碼讓CustomObject
使用新的NSZone。
void allocCustomObjectsWithCustomNSZone() { static NSMutableSet *objs = nil; if (objs == nil) { objs = [NSMutableSet new]; } NSZone *customZone = NSCreateZone(1024, 1024, YES); NSSetZoneName(customZone, @"Custom Object Zone"); for (int i = 0; i < 1000; ++i) { CustomObject *obj = [CustomObject allocWithZone:customZone]; [objs addObject:obj]; } } 複製代碼
代碼建立了1000個CustomObject
對象,而且嘗試使用新建的Zone。咱們用heap工具看看結果。首先使用Activity Monitor找到進程的PID,在命令行中執行
heap PID
複製代碼
執行的結果大體以下。
...... Process 25073: 3 zones Zone DefaultMallocZone_0x1004c9000: Overall size: 196992KB; 13993 nodes malloced for 160779KB (81% of capacity); largest unused: [0x102800000-171072KB] Zone Custom Object Zone_0x1004fe000: Overall size: 1024KB; 1 nodes malloced for 1KB (0% of capacity); largest unused: [0x102200000-1024KB] Zone GFXMallocZone_0x1004d8000: Overall size: 0KB All zones: 13994 nodes malloced - 160779KB Zone DefaultMallocZone_0x1004c9000: 13993 nodes - Sizes: 160KB[1000] 64.5KB[1] 16.5KB[1] 13.5KB[1] 4.5KB[3] 2KB[3] 1.5KB[12] 1KB[1] 704[1] 576[13] 528[4] 512[2] 480[1] 464[1] 448[2] 432[1] 400[1] 384[2] 368[1] 352[1] 336[2] 320[1] 272[8] 256[1] 240[4] 208[10] 192[5] 176[3] 160[5] 144[28] 128[48] 112[43] 96[83] 80[519] 64[3044] 48[5415] 32[3640] 16[82] Zone Custom Object Zone_0x1004fe000: 1 nodes - Sizes: 32[1] Zone GFXMallocZone_0x1004d8000: 0 nodes All zones: 13994 nodes malloced - Sizes: 160KB[1000] 64.5KB[1] 16.5KB[1] 13.5KB[1] 4.5KB[3] 2KB[3] 1.5KB[12] 1KB[1] 704[1] 576[13] 528[4] 512[2] 480[1] 464[1] 448[2] 432[1] 400[1] 384[2] 368[1] 352[1] 336[2] 320[1] 272[8] 256[1] 240[4] 208[10] 192[5] 176[3] 160[5] 144[28] 128[48] 112[43] 96[83] 80[519] 64[3044] 48[5415] 32[3641] 16[82] Found 523 ObjC classes Found 56 CFTypes ----------------------------------------------------------------------- Zone DefaultMallocZone_0x1004c9000: 13993 nodes (164637440 bytes) COUNT BYTES AVG CLASS_NAME TYPE BINARY ===== ===== === ========== ==== ====== 12771 779136 61.0 non-object 1000 163840000 163840.0 CustomObject ObjC VMResearch 49 2864 58.4 CFString ObjC CoreFoundation 21 1344 64.0 pthread_mutex_t C libpthread.dylib 20 1280 64.0 CFDictionary ObjC CoreFoundation 18 2368 131.6 CFDictionary (Value Storage) C CoreFoundation 16 2304 144.0 CFDictionary (Key Storage) C CoreFoundation 8 512 64.0 CFBasicHash CFType CoreFoundation 7 560 80.0 CFArray ObjC CoreFoundation 6 768 128.0 CFPrefsPlistSource ObjC CoreFoundation 6 480 80.0 OS_os_log ObjC libsystem_trace.dylib 5 160 32.0 NSMergePolicy ObjC CoreData 4 384 96.0 NSLock ObjC Foundation ...... ----------------------------------------------------------------------- Zone Custom Object Zone_0x1004fe000: 1 nodes (32 bytes) COUNT BYTES AVG CLASS_NAME TYPE BINARY ===== ===== === ========== ==== ====== 1 32 32.0 non-object ----------------------------------------------------------------------- Zone GFXMallocZone_0x1004d8000: 0 nodes (0 bytes) 複製代碼
一共有3個zone,Zone Custom Object Zone_0x1004fe000: 1 nodes (32 bytes)
就是咱們建立的NSZone,不過它裏面只有一個節點,共32bytes,若是你不設置Zone的name,它會是0bytes。因此咱們能夠推導出這32bytes是用來存儲Zone自己的信息的。咱們建立的1000個CustomObject
其實在Zone DefaultMallocZone_0x1004c9000
裏,也就是系統默認建立的NSZone。若是你真的想用Zone內存機制,可使用malloc_zone_t。經過下面的代碼能夠在自定義的zone上malloc內存塊。
void allocCustomObjectsWithCustomMallocZone() { malloc_zone_t *customZone = malloc_create_zone(1024, 0); malloc_set_zone_name(customZone, "custom malloc zone"); for (int i = 0; i < 1000; ++i) { malloc_zone_malloc(customZone, 300 * 4096); } } 複製代碼
再次使用heap工具查看。我只截取了custom malloc zone的內容。有1001個node,也就是1000個malloc_zone_malloc出來的內存塊加上zone自己的信息所佔的內存塊。
-----------------------------------------------------------------------
Zone custom malloc zone_0x1004fe000: 1001 nodes (1228800032 bytes)
COUNT BYTES AVG CLASS_NAME TYPE BINARY
===== ===== === ========== ==== ======
1001 1228800032 1227572.4 non-object
複製代碼
咱們可使用malloc_destroy_zone(customZone)
一次性釋放上面分配的全部內存。
本文主要介紹了iOS (OSX)系統中VM的相關原理,以及如何使用VM Track模板來分析VM Regions,本文只是關注了MALLOC相關的幾個VM Region,還有其餘專用的一些VM Region,經過研究他們的內存分配,能夠有針對性的對內存進行優化,這就是接下來要作的事情。