iOS記憶體深入探索之VM Tracker

handyTool發表於2019-02-27

什麼是VM Tracker

VM Tracker是Xcode Instruments自帶的一個記憶體分析工具,可以幫助你快速檢視虛擬記憶體塊的用量狀態以及根據虛擬記憶體塊的tag進行分類。如果你想知道關於虛擬記憶體的相關知識,可以先閱讀探索iOS記憶體分配這篇文章,如果你對虛擬記憶體以及VM Region不太瞭解的話,閱讀下面的內容可能會有些障礙。想要使用VM Tracker,使用Instruments的Allocations模版即可。如果模版自帶的VM Tracker不顯示資訊,可以用右邊的加號再新增一個VM Tracker。

VM Tracker列屬性解析

iOS記憶體深入探索之VM Tracker
上面是一個空的iOS App的VM Tracker示意圖。一共有9列,下面我來一一解釋它們的含義。

  • % of Res, 當前Type的VM Regions總Resident Size佔比。
  • Type,VM Regions的Type,AllDirty算是統計性質的Type,__TEXT表示程式碼段的記憶體對映,__DATA表示資料段的記憶體對映。MALLOC_TINY,MALLOC_LARGE,CG Image等Type可以從VM Region的Extend Info中讀取出來,後面會著重介紹。
  • # Regs,當前Type的VM Region總數。
  • Path,VM Region是從哪個檔案對映過來,因為有些類似於__DATA和mapped file的記憶體塊是從檔案直接對映過來的。
  • Resident Size,使用的實體記憶體量。
  • Dirty Size,使用中的實體記憶體塊如果不交換到硬碟儲存狀態就不能複用,那麼就是Dirty的記憶體塊,比如你主動malloc出來的記憶體塊,如果不保留其中的狀態就把它給別人用,那你肯定就無法恢復這個記憶體塊的資訊,所以它是Dirty的。如果是一個對映到記憶體的檔案,就算使用它的記憶體塊,還是可以重新從磁碟載入檔案到記憶體的,所以是非Dirty的,比如最上面圖中的mapped file那一行,你可以看到Dirty Size是0。
  • Swapped Size, 在OSX中,不活躍的記憶體頁可以被交換到硬碟,這是被交換的大小。在iOS中,只有非Dirty的記憶體頁可以被交換,或者說是被解除安裝。
  • Virtual Size,VM Regions所佔虛擬記憶體的大小
  • Res. %,Resident Size在Virtual Size中的佔比

使用vm_allocate自定義VM Region

我們可以使用vm_allocate方法申請一塊虛擬記憶體。下面是具體程式碼。

vm_address_t address;
vm_size_t size = 1024 * 1024 * 100;
vm_allocate((vm_map_t)mach_task_self(), &address, size, VM_MAKE_TAG(200) | VM_FLAGS_ANYWHERE);
複製程式碼

上面的程式碼申請了一塊100M的虛擬記憶體,(vm_map_t)mach_task_self()表示在自己的程式空間內申請。size的單位是byte。 VM_MAKE_TAG(200)是給你申請的記憶體塊提供一個Tag標記。我這裡提供了一個200數值作為標記,後面我會具體介紹這個數值在VM Tracker中的作用。最後我們用VM Tracker看一下我們自己分配的虛擬記憶體塊。

iOS記憶體深入探索之VM Tracker

你可能會注意到這塊記憶體塊的Resident Size和Dirty Size都是0KB,因為我們並沒有使用這塊記憶體,所以並沒有虛擬記憶體被關聯到實體記憶體上去。你可以嘗試使用這塊記憶體,然後去VM Tracker觀察變化。比如使用下面的方式填充記憶體塊。

for (int i = 0; i < 1024 * 1024 * 100; ++i) {
  *((char *)address + i) = 0xab;
}
複製程式碼

VM Region的Type

接下來我們來介紹記憶體塊的Type,我曾經思考很久VM Tracker是如何識別出每個記憶體塊的Type的。比如MALLOC_TINY,MALLOC_SMALL,ImageIO等等。答案就在vm_allocate方法的最後一個引數flags。flags可以分成2個部分。VM_FLAGS_ANYWHERE屬於flags裡控制記憶體分配方式的flag,它表示可以接受任意位置的記憶體分配。它的巨集定義如下。

#define VM_FLAGS_ANYWHERE	0x0001
複製程式碼

從定義可以看出,2個位元組就可以儲存它,int有4個位元組,還剩下2個就可以用來儲存標記記憶體型別的Type了。蘋果提供了VM_MAKE_TAG巨集幫助我們快速設定Type。VM_MAKE_TAG實際上做了一件很簡單的事情,把值左移24個bit,也就是3個位元組,所以系統留給了我們1個位元組來表示記憶體的型別。下面是VM_MAKE_TAG的巨集定義。

#define VM_MAKE_TAG(tag) ((tag) << 24)
複製程式碼

實際上蘋果已經內建了很多預設的Type,下面列出一部分。

#define VM_MEMORY_MALLOC 1
#define VM_MEMORY_MALLOC_SMALL 2
#define VM_MEMORY_MALLOC_LARGE 3
#define VM_MEMORY_MALLOC_HUGE 4
#define VM_MEMORY_SBRK 5// uninteresting -- no one should call
#define VM_MEMORY_REALLOC 6
#define VM_MEMORY_MALLOC_TINY 7
#define VM_MEMORY_MALLOC_LARGE_REUSABLE 8
#define VM_MEMORY_MALLOC_LARGE_REUSED 9
複製程式碼

如果我們使用VM_MEMORY_MALLOC_HUGE來作為Type,再用VM Tracker觀察會怎麼樣呢?下面是記憶體分配的程式碼。

vm_address_t address;
vm_size_t size = 1024 * 1024 * 100;
vm_allocate((vm_map_t)mach_task_self(), &address, size, VM_MAKE_TAG(VM_MEMORY_MALLOC_HUGE) | VM_FLAGS_ANYWHERE);
複製程式碼

下面是VM Tracker的截圖。

iOS記憶體深入探索之VM Tracker

很明顯VM Tracker認出了這塊記憶體,並且將它的Type設定為MALLOC_HUGE。如果你想使用vm_allocate來分配和管理大記憶體,也可以設定一個Type,方便快速定位到自己的虛擬記憶體塊。

總結

本文主要介紹了VM Tracker中關於虛擬記憶體的一些概念,以及如何自行分配虛擬記憶體。瞭解了這些之後,在分析記憶體暴漲或者洩漏時就有了新的思路,而不僅僅是侷限於基於malloc記憶體塊的記憶體分析了。

相關文章