物理內(nèi)存
一個(gè)設(shè)備的 RAM 大小尝抖。Mac的RAM大小不固定,用戶可以隨便擴(kuò)展迅皇。而iphone是固定不變的昧辽,以下是維基百科上的資料:
簡單來說,iPhone 8(不包括 plus) 和 iPhone 7(不包括 plus)及之前都是 2G 內(nèi)存登颓,iPhone 6 和 6 plus 及之前都是 1G 內(nèi)存搅荞。
虛擬內(nèi)存
虛擬內(nèi)存是計(jì)算機(jī)系統(tǒng)內(nèi)存管理的一種技術(shù)。它使得應(yīng)用程序認(rèn)為它擁有連續(xù)的可用的內(nèi)存(一個(gè)連續(xù)完整的地址空間)框咙,而實(shí)際上咕痛,它通常是被分隔成多個(gè)物理內(nèi)存碎片,還有部分暫時(shí)存儲(chǔ)在外部磁盤存儲(chǔ)器上喇嘱,在需要時(shí)進(jìn)行數(shù)據(jù)交換 (iOS并無數(shù)據(jù)交換)茉贡。
從系統(tǒng)角度來說,每個(gè)進(jìn)程都有一個(gè)自己私有的相同大小的虛擬內(nèi)存空間者铜。虛擬內(nèi)存空間大小跟內(nèi)存地址表示位數(shù)有關(guān)(也就是指針的位數(shù))腔丧,32位系統(tǒng)下為32位, 64位系統(tǒng)下為64位。所以32位系統(tǒng)的虛擬內(nèi)存是4GB作烟,而64位是 18EB(1EB = 1000PB, 1PB = 1000TB)愉粤。
操作系統(tǒng)的位數(shù) >= CPU的位數(shù),比如64位CPU上可以運(yùn)行32位系統(tǒng)拿撩,只是沒有充分利用CPU的運(yùn)算能力衣厘。 而ios系統(tǒng)一般跟CPU保持位數(shù)一致,而iphone 5s(A7)及以后的CPU都是64位的压恒。
系統(tǒng)將虛擬內(nèi)存和物理內(nèi)存分割成統(tǒng)一大小的單元头滔,叫做頁(page)。在 OS X 和早期的iOS里涎显,頁大小均為4K坤检;之后基于A7和A8的iOS里,采用虛擬內(nèi)存每頁16K期吓,物理內(nèi)存每頁4K早歇;基于A9或更新CPU的iOS里倾芝,頁大小均為16K.
CPU有個(gè)內(nèi)存管理單元(MMU), 它維護(hù)了一張頁表(page table), 可以將虛擬地址映射到物理地址。用戶訪問虛擬地址時(shí)箭跳,會(huì)自動(dòng)被MMU轉(zhuǎn)換成物理地址晨另。當(dāng)CPU訪問的虛擬地址并未映射到物理地址時(shí),CPU會(huì)觸發(fā)頁錯(cuò)誤(page fault)中斷, 并暫停當(dāng)前執(zhí)行的程序代碼谱姓,然后分配一塊干凈的物理內(nèi)存借尿,從磁盤中加載所需的一頁數(shù)據(jù)到該物理內(nèi)存,同時(shí)更新頁表屉来,然后繼續(xù)執(zhí)行程序代碼路翻。
當(dāng)進(jìn)程向系統(tǒng)申請內(nèi)存時(shí),系統(tǒng)也并不會(huì)直接返回物理內(nèi)存的地址茄靠,而是返回一個(gè)虛擬內(nèi)存地址茂契。僅當(dāng)CPU需要訪問該虛擬內(nèi)存地址時(shí),系統(tǒng)才會(huì)分配并映射到物理內(nèi)存慨绳。
VM Object
進(jìn)程的虛擬地址空間包含了多個(gè)區(qū)域(VM Region), 每個(gè)VM Region管理一段連續(xù)的虛擬內(nèi)存頁掉冶,這些頁擁有相同的屬性(如讀寫權(quán)限、是否是 wired脐雪,也就是是否能被 page out)厌小。舉幾個(gè)例子:
- mapped file,即映射到磁盤的一個(gè)文件
- __TEXT战秋,r-x召锈,多數(shù)為二進(jìn)制
- __DATA,rw-获询,為可讀寫數(shù)據(jù)
- MALLOC_(SIZE)涨岁,顧名思義是 malloc 申請的內(nèi)存
每個(gè) VM Region 對應(yīng)一個(gè)內(nèi)核數(shù)據(jù)結(jié)構(gòu),名為 VM Object吉嚣。Object 會(huì)記錄這個(gè) Region 內(nèi)存的屬性:
- Resident pages - 已經(jīng)被映射到物理內(nèi)存的虛擬內(nèi)存頁列表
- Size - 所有內(nèi)存頁所占區(qū)域的大小
- Pager - 用來處理內(nèi)存頁在硬盤和物理內(nèi)存中交換問題
- Attributes - 這塊內(nèi)存區(qū)域的屬性梢薪,比如讀寫的權(quán)限控制
- Shadow - 用作(copy-on-write)寫時(shí)拷貝的優(yōu)化
- Copy - 用作(copy-on-write)寫時(shí)拷貝的優(yōu)化
VM Object的Pager一般有三種類型:
- default pager 用于磁盤交換內(nèi)存;
- vnode pager 用于將磁盤文件映射到內(nèi)存
- device pager 將地址映射到非主存的硬件上尝哆,比如PCI內(nèi)存秉撇、幀緩沖內(nèi)存等,很多I/O Kit的調(diào)用背后用了這個(gè)秋泄。
除了以上Pager琐馆, VM Object還可以關(guān)聯(lián)另外一個(gè)VM Object, 通過這種引用方式實(shí)現(xiàn)寫時(shí)復(fù)用恒序。 這種機(jī)制可以允許不同進(jìn)程或同一進(jìn)程里的不同代碼段共享同一內(nèi)存頁瘦麸,直到有一方試圖寫入數(shù)據(jù)時(shí),才會(huì)真正的拷貝內(nèi)存頁歧胁。
內(nèi)核常駐內(nèi)存(Wired Memory)
內(nèi)核常駐內(nèi)存存儲(chǔ)了內(nèi)核代碼和數(shù)據(jù)結(jié)構(gòu)滋饲,不允許換出到磁盤上厉碟。應(yīng)用(Application)、開發(fā)框架(Framework)和其他用戶級(jí)別的軟件無法分配內(nèi)核常駐內(nèi)存屠缭。然而箍鼓,他們可以影響內(nèi)核常駐內(nèi)存的大小。舉個(gè)例子呵曹,一個(gè)而應(yīng)用創(chuàng)建了線程和端口時(shí)款咖,會(huì)隱式的分配常駐內(nèi)存來存放對應(yīng)的內(nèi)核資源铐殃。
- Process: 16k
- Thread: blocked in a continuation—5 k; blocked—21 k
- Mach port: 116b
- Mapping: 32 b
- Library: 2k plus 200b for each task that uses it
- Memory region: 160b
除了應(yīng)用運(yùn)行產(chǎn)生的內(nèi)核常駐內(nèi)存砍聊,內(nèi)核自己本身也會(huì)創(chuàng)建一些常駐內(nèi)存來存儲(chǔ)以下實(shí)體:
- VM Objects
- the virtual memory buffer cache
- I/O buffer caches
- drivers
內(nèi)核頁表
內(nèi)核維護(hù)了三種狀態(tài)的頁表:
- 活躍的內(nèi)存頁(active page):內(nèi)存頁已經(jīng)被映射到物理內(nèi)存中背稼,而且近期被訪問過,處于活躍狀態(tài)词疼。
- 非活躍的內(nèi)存頁(inactive page):內(nèi)存頁已經(jīng)被映射到物理內(nèi)存中俯树,但是近期沒有被訪問過许饿。
- 可用的內(nèi)存頁(free page):沒有關(guān)聯(lián)到虛擬內(nèi)存頁的物理內(nèi)存頁集合陋率。
如果可用內(nèi)存頁(free page)數(shù)量低于一個(gè)閾值時(shí)(根據(jù)物理內(nèi)存大小定義的)秽晚,系統(tǒng)會(huì)去平衡一下這個(gè)隊(duì)列赴蝇。 具體就是從非活躍內(nèi)存里獲取劲蜻。在OS X里是通過將非活躍頁的數(shù)據(jù)交換到磁盤后先嬉,再將非活躍頁清理并放置到可用內(nèi)存頁中坝初。
Page Out 過程
在OSX上系統(tǒng)會(huì)將不活躍的內(nèi)存塊寫入硬盤鳄袍,一般稱之為Swap out
或Page out
拗小。
由于虛擬內(nèi)存的空間遠(yuǎn)遠(yuǎn)大于物理內(nèi)存哀九,在任意一個(gè)時(shí)間點(diǎn)阅束,虛擬內(nèi)存中的一個(gè)頁并不一定總是在物理內(nèi)存中息裸,而是可能被暫時(shí)存到了磁盤上呼盆,這樣物理內(nèi)存便可以暫時(shí)釋放這部分空間厨幻,供優(yōu)先級(jí)更高的任務(wù)使用况脆,因此磁盤可以作為 backing store
以擴(kuò)展物理內(nèi)存(MacOS 中有格了,iOS 沒有)笆搓。
具體實(shí)現(xiàn)是满败,內(nèi)核遍歷活躍頁表和非活躍頁表算墨,并執(zhí)行以下操作:
- 如果活躍頁表的某頁最近沒有被訪問過净嘀,則將它移入非活躍頁表暑刃。
- 如果非活躍頁表的某頁最近被訪問過岩臣,則內(nèi)核會(huì)找出它所在的VM Object.
- 如果VM Object沒有關(guān)聯(lián)過pager篙议,那就給他創(chuàng)建一個(gè)default pager
- VM Object的default pager嘗試將該頁寫入磁盤交互區(qū)(backing store)
- 如果pager完成寫入驯遇,內(nèi)核會(huì)釋放該頁占用的物理內(nèi)存会涎,并將其移入可用頁表里在塔。
Page In 過程
當(dāng)程序代碼試圖訪問某個(gè)還未映射到物理內(nèi)存的虛擬地址時(shí),會(huì)觸發(fā)兩種內(nèi)存訪問錯(cuò)誤(fault):
-
soft fault
: 要訪問的頁數(shù)據(jù)在物理內(nèi)存里篱蝇,但并未映射到當(dāng)前進(jìn)程的虛擬地址空間零截。 -
hard fault
: 要訪問的頁數(shù)據(jù)并未在物理內(nèi)存里,可能是之前被交換到磁盤了弧哎,也可能是文件的一部分還未映射到內(nèi)存撤嫩。 這也是最經(jīng)典的頁錯(cuò)誤(page fault)
任何一種錯(cuò)誤發(fā)生時(shí)序攘,內(nèi)核會(huì)定位到當(dāng)前region關(guān)聯(lián)的VM Object丈牢,并且遍歷VM Object的常駐內(nèi)存(resident pages)頁表赡麦,如果目標(biāo)頁在該表中泛粹,那么就觸發(fā)soft fault
,否則觸發(fā)hard fault
晶姊。
針對soft fault
,內(nèi)核會(huì)將對應(yīng)的物理頁映射到當(dāng)前進(jìn)程的虛擬地址空間里,并且將該頁標(biāo)記為活躍蒙挑。針對hard fault
忆蚀,VM Object的pager將從磁盤backing store
或文件中查找對應(yīng)的頁數(shù)據(jù)馋袜,并將其復(fù)制到物理內(nèi)存里,然后加入到活躍頁表里泽台。
Swap In/Out & Page In/Out
磁盤內(nèi)部有一個(gè)區(qū)域叫做交換空間(Swap Space)怀酷,MMU(內(nèi)存管理單元) 會(huì)將暫時(shí)不用的內(nèi)存塊內(nèi)容寫在該交互空間上,這就是Swap Out笔横;當(dāng)需要時(shí)候再從Swap Space中讀取到內(nèi)存中吹缔,這就是Swap In厢塘;Swap in和swap out的操作都是比較耗時(shí)的, 頻繁的Swap in和Swap out操作非常影響系統(tǒng)性能抓半;
Page In/Out和 Swap In/Out 概念類似笛求,只不過Page In/Out是將某些頁的數(shù)據(jù)寫到內(nèi)存/從內(nèi)存寫回磁盤交互區(qū);而Swap In/Out是將整個(gè)地址空間的數(shù)據(jù)寫到內(nèi)存/從內(nèi)存寫回磁盤交互區(qū)蜂嗽;本質(zhì)都是交互機(jī)制植旧。
-
macOS支持這類交換機(jī)制界阁,但是iOS不支持泡躯;主要有兩方面考慮:
- 移動(dòng)設(shè)備的閃存讀寫次數(shù)有限较剃,頻繁寫會(huì)降低壽命惰拱;
- 相比PC機(jī)偿短,移動(dòng)設(shè)備閃存空間有限(15年6s最小存儲(chǔ)空間16GB降传、最大128GB婆排;19年XS Max最小64GB,最大521GB)
Memory Compression
由于閃存容量和讀寫壽命的限制翼悴,iOS 上沒有Disk swap機(jī)制鹦赎,取而代之使用Memory Compression
。等壓縮后內(nèi)存也不夠用后陪踩,iOS上則會(huì)通知App,讓App清理內(nèi)存傻谁,也就是我們熟知的Memory Warning
。
內(nèi)存壓縮技術(shù)是從 OS X Mavericks (10.9) 開始引入的(iOS 則是 iOS 7 開始)态蒂,可以參考官方文檔:OS X Mavericks Core Technology Overview钾恢。該技術(shù)在內(nèi)存緊張時(shí)能夠?qū)⒆罱褂眠^的內(nèi)存占用壓縮至原有大小的一半以下刑桑,并且能夠在需要時(shí)解壓復(fù)用祠斧。它在節(jié)省內(nèi)存的同時(shí)提高了系統(tǒng)的響應(yīng)速度,其特點(diǎn)可以歸結(jié)為:
- 減少了不活躍內(nèi)存占用
- 改善電源效率吴超,通過壓縮減少磁盤IO帶來的損耗
- 壓縮/解壓十分迅速,能夠盡可能減少 CPU 的時(shí)間開銷
- 支持多核并行操作
這里要注意鸟悴,對于已經(jīng)被壓縮過的內(nèi)存,如果嘗試釋放其中一部分震贵,則會(huì)先將它解壓。而解壓過程帶來的內(nèi)存增大寇甸,可能適得其反, 典型的場景就是內(nèi)存告警時(shí)清理NSDictionary緩存數(shù)據(jù)。對于緩存數(shù)據(jù)或可重建數(shù)據(jù),應(yīng)盡量使用NSCache或NSPurableData偏窝,收到內(nèi)存警告時(shí)伦意,系統(tǒng)自動(dòng)處理內(nèi)存釋放操作,并且是線程安全的离钝。
內(nèi)存類型
Virtual Memory
OS X虛擬內(nèi)存分配大小
Virtual Memory = Dirty Memory + Clean Memory + Swapped Memory
iOS 虛擬內(nèi)存分配大小
Virtual Memory = Dirty Memory + Clean Memory + Compressed Memory
獲取App申請到的所有虛擬內(nèi)存:
- (int64_t)memoryVirtualSize {
struct task_basic_info info;
mach_msg_type_number_t size = (sizeof(task_basic_info_data_t) / sizeof(natural_t));
kern_return_t ret = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
if (ret != KERN_SUCCESS) {
return 0;
}
return info.virtual_size;
}
Clean Memory
能夠被系統(tǒng)清理出內(nèi)存且在需要時(shí)能重新加載數(shù)據(jù)的Page:
- 應(yīng)用或系統(tǒng)庫的二進(jìn)制可執(zhí)行文件
- Memory mapped files:image.jpg,.data,.model 文件等碘橘,這些文件通常是只讀的,映射的內(nèi)存頁可以被系統(tǒng)直接釋放掉错负,需需要的時(shí)候再從文件載入到內(nèi)存。
- Framework:__DATA_CONST字段识颊,但是有一點(diǎn)需要注意:這個(gè)字段在創(chuàng)建的時(shí)候是 Clean Page 類型的,但是當(dāng)程序運(yùn)行起來的時(shí)候刃跛,如果我們對系統(tǒng)方法進(jìn)行了 Swizzling 就會(huì)把這個(gè)內(nèi)存頁變成 Dirty Page检号。
- malloc分配后但還未寫過的內(nèi)存(其實(shí)僅分配了虛擬內(nèi)存,真正寫時(shí)才會(huì)分配物理內(nèi)存)凹蜂。
Dirty Memory
主要強(qiáng)調(diào)不可被重復(fù)使用的內(nèi)存,準(zhǔn)確講是如果不交換到硬盤保存狀態(tài)就不能復(fù)用的內(nèi)存(IOS系統(tǒng)根本沒有swap機(jī)制)卿啡。
- 所有堆上的對象(array,uiview官辽,string等等)。
- 圖片解析緩沖(CGRasterData,imageIO)俗批。
- Framework 的__DATA 和 __DATA_DIRTY部分
iOS中的內(nèi)存警告,只會(huì)釋放clean memory干像。因?yàn)閕OS認(rèn)為dirty memory有數(shù)據(jù)驰弄,不能清理麻汰。所以應(yīng)盡量避免dirty memory過大
Clean和Dirty示例
int *array = malloc(20000 * sizeof(int)); // 第1步
array[0] = 32 // 第2步
array[19999] = 64 // 第3步
- 第一步,申請一塊長度為80000 字節(jié)的內(nèi)存空間戚篙,按照一頁 16KB 來計(jì)算五鲫,就需要 6 頁內(nèi)存來存儲(chǔ)。當(dāng)這些內(nèi)存頁開辟出來的時(shí)候臣镣,它們都是 Clean 的辅愿;
- 第二步智亮,向處于第一頁的內(nèi)存寫入數(shù)據(jù)時(shí)忆某,第一頁內(nèi)存會(huì)變成 Dirty;
- 第三步阔蛉,當(dāng)向處于最后一頁的內(nèi)存寫入數(shù)據(jù)時(shí)弃舒,這一頁也會(huì)變成 Dirty;
Compressed Memory
注意状原,用vvmap等工具測量的Compressed Memory都是壓縮之前的大小
Resident Memory
已經(jīng)被映射到虛擬內(nèi)存中的物理內(nèi)存聋呢。存在一些“非代碼執(zhí)行開銷”,如系統(tǒng)和應(yīng)用二進(jìn)制加載的內(nèi)存颠区。
這里存在兩種Resident Memory削锰,系統(tǒng)的和我們APP的:
All Resident = 系統(tǒng)Resident + APP Resident
APP Resident = Dirty Memory + Clean Memory that loaded in pysical memory(_TEXT + _OBJC_RO + Other)
其中系統(tǒng)Resident
主要就是dyld_shared_cache
(動(dòng)態(tài)庫共享緩存),所有APP的虛擬內(nèi)存對動(dòng)態(tài)庫的物理內(nèi)存映射都是映射到這塊的毕莱,我們運(yùn)行不同的app查看dyld_shared_cache
的地址都是一樣的:
獲取App消耗的Resident Memory:
#import <mach/mach.h>
- (int64_t)memoryResidentSize {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(task_basic_info_data_t) / sizeof(natural_t);
kern_return_t ret = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
if (ret != KERN_SUCCESS) {
return 0;
}
return info.resident_size;
}
Memory Footprint
App消耗的實(shí)際物理內(nèi)存器贩,具體定義在官方文檔 Minimizing your app’s Memory Footprint 里有說明:
Refers to the total current amount of system memory that is allocated to your app.
經(jīng)過測試,Xcode Debug Navigator朋截、系統(tǒng)活動(dòng)監(jiān)控器蛹稍、footprint 命令工具和代碼中phys_footprint 得到的數(shù)據(jù)是一致的,都表示當(dāng)前App消耗的實(shí)際物理內(nèi)存部服。
內(nèi)核代碼里的注釋:
/*
* phys_footprint
* Physical footprint: This is the sum of:
* + (internal - alternate_accounting)
* + (internal_compressed - alternate_accounting_compressed)
* + iokit_mapped
* + purgeable_nonvolatile
* + purgeable_nonvolatile_compressed
* + page_table
*
* internal
* The task's anonymous memory, which on iOS is always resident.
*
* internal_compressed
* Amount of this task's internal memory which is held by the compressor.
* Such memory is no longer actually resident for the task [i.e., resident in its pmap],
* and could be either decompressed back into memory, or paged out to storage, depending
* on our implementation.
*
* iokit_mapped
* IOKit mappings: The total size of all IOKit mappings in this task, regardless of
clean/dirty or internal/external state].
*
* alternate_accounting
* The number of internal dirty pages which are part of IOKit mappings. By definition, these pages
* are counted in both internal *and* iokit_mapped, so we must subtract them from the total to avoid
* double counting.
*/
App消耗的實(shí)際物理內(nèi)存唆姐,包括:
- Dirty Memory
- Compressed Memory
- Page Table
- IOKit used
- NSCache, Purgeable等
通過footprint命令的輸出可以知道廓八,F(xiàn)ootprint主要是Dirty部分(可以粗略理解二者等價(jià))奉芦,也就是我們可以控制優(yōu)化的部分。注意: Resident Memory 包含了 Memory Footprint
剧蹂。
獲取App的Footprint:
#import <mach/mach.h>
- (int64_t)memoryPhysFootprint {
task_vm_info_data_t vmInfo;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t ret = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vmInfo, &count);
if (ret != KERN_SUCCESS) {
return 0;
}
return vmInfo.phys_footprint;
}
XNU中Jetsam
判斷內(nèi)存過大声功,使用的也是phys_footprint,而非resident size国夜。
Jetsam: 由于iOS設(shè)備不存在交換區(qū)導(dǎo)致的內(nèi)存受限减噪,所以iOS內(nèi)核不得不把一些優(yōu)先級(jí)不高或者占用內(nèi)存過大的殺掉。
內(nèi)存監(jiān)控工具
除了系統(tǒng)提供的活動(dòng)監(jiān)視器
來觀察內(nèi)存车吹,還有很多更強(qiáng)大的工具來定位內(nèi)存問題筹裕。
vm_stat
在 macOS 上我們在終端運(yùn)行 vm_stat
可以看到以下系統(tǒng)整體內(nèi)存信息:
? darwin-xnu git:(master) vm_stat
Mach Virtual Memory Statistics: (page size of 4096 bytes)
Pages free: 349761.
Pages active: 1152796.
Pages inactive: 1090213.
Pages speculative: 22734.
Pages throttled: 0.
Pages wired down: 979685.
Pages purgeable: 519551.
"Translation faults": 300522536.
Pages copy-on-write: 16414066.
Pages zero filled: 94760760.
Pages reactivated: 4424880.
Pages purged: 4220936.
File-backed pages: 480042.
Anonymous pages: 1785701.
Pages stored in compressor: 2062437.
Pages occupied by compressor: 598535.
Decompressions: 4489891.
Compressions: 11890969.
Pageins: 6923471.
Pageouts: 38335.
Swapins: 87588.
Swapouts: 432061.
這個(gè)系統(tǒng)命令就是通過 host_statistics64()
獲取的,代碼參考窄驹。
這才是最終的系統(tǒng)內(nèi)存占用情況朝卒,以 byte 為單位:
(active_count + wired_count + speculative_count + compressor_page_count) * page_size
footprint
footprint -p 14022 --wired --swapped
Xcode Navigator
初略展示了真實(shí)的物理內(nèi)存消耗。顏色表明了內(nèi)存占用是否合理乐埠。Xcode Navigator = footprint + 調(diào)試需要抗斤。不跟蹤VM囚企。往往初略觀察App的內(nèi)存占用情況,不能作為精確的參考瑞眼。
Instuments Allocations
這里顯示的內(nèi)存龙宏,其實(shí)只是整個(gè)App占用內(nèi)存的一部分,即開發(fā)者自行分配的內(nèi)存伤疙,如各種類實(shí)例等银酗。
- 主要是MALLOC_XXX, VM Region, 以及部分App進(jìn)程創(chuàng)建的VM Region。
- 非動(dòng)態(tài)的內(nèi)存徒像,及部分其他動(dòng)態(tài)庫創(chuàng)建的VM Region并不在Allocations的統(tǒng)計(jì)范圍內(nèi)黍特。
- 主程序或動(dòng)態(tài)庫的_DATA數(shù)據(jù)段、Stack函數(shù)棧锯蛀,并非通過malloc分配灭衷,因此不在Allocations統(tǒng)計(jì)內(nèi)。
All Heap Allocations:
- malloc
- CFData
All Anonymous VM
無法由開發(fā)者直接控制旁涤,一般由系統(tǒng)接口調(diào)用申請的翔曲。例如圖片之類的大內(nèi)存,屬于All Anonymous VM -> VM: ImageIO_IOSurface_Data拭抬,其他的還有IOAccelerator與IOSurface等跟GPU關(guān)系比較密切的.
Instruments VM Tracker
Instruments里打開Allocations
就可以看到有VM Tracker
了部默,記得要開啟自動(dòng)捕獲快照后才能展示結(jié)果
上面是一個(gè)空的iOS App的VM Tracker示意圖。一共有9列造虎,下面我來一一解釋它們的含義傅蹂。
-
% of Res
, 當(dāng)前Type的VM Regions總Resident Size占比算凿。 -
Type
份蝴,VM Regions的Type,All和Dirty算是統(tǒng)計(jì)性質(zhì)的Type氓轰,__TEXT表示代碼段的內(nèi)存映射婚夫,__DATA表示數(shù)據(jù)段的內(nèi)存映射。 -
# Regs
署鸡,當(dāng)前Type的VM Region總數(shù)案糙。 -
Path
,VM Region是從哪個(gè)文件映射過來靴庆,因?yàn)橛行╊愃朴赺_DATA和mapped file的內(nèi)存塊是從文件直接映射過來的时捌。 -
Resident Size
,使用的物理內(nèi)存量炉抒。 -
Dirty Size
奢讨,使用中的物理內(nèi)存塊如果不交換到硬盤保存狀態(tài)就不能復(fù)用,那么就是Dirty的內(nèi)存塊焰薄。 -
Swapped Size
, 在OSX中拿诸,不活躍的內(nèi)存頁可以被交換到硬盤扒袖,這是被交換的大小。在iOS中亩码,只有非Dirty的內(nèi)存頁可以被交換季率,或者說是被卸載。 -
Virtual Size
蟀伸,VM Regions所占虛擬內(nèi)存的大小 -
Res. %
蚀同,Resident Size在Virtual Size中的占比
Xcode Memory Debugger
該工具可以非常方便地查看所有對象的內(nèi)存使用情況缅刽、依賴關(guān)系啊掏,以及循環(huán)引用等。如果將其導(dǎo)出為memgraph文件衰猛,也可以使用一些命令來進(jìn)行分析:
vmmap
vmmap memory-info.memgraph
-
查看摘要
vmmap --summary memory-info.memgraph
結(jié)合shell中的grep迟蜜、awk等命令,可以獲得任何想要的內(nèi)存數(shù)據(jù)啡省。
摘要里的字段含義跟VM Tracker里一致:
查看所有dylib的Dirty Pages的總和
vmmap -pages memory-info.memgraph | grep '.dylib' | awk '{sum += $6} END { print "Total Dirty Pages:"sum}'
查看CG image相關(guān)的內(nèi)存數(shù)據(jù)
vmmap memory-info.memgraph | grep 'CG image'
heap
查看堆內(nèi)存
查看Heap上的所有對象
heap memory-info.memgraph
按照內(nèi)存大小來排序
heap memory-info.memgraph -sortBySize
查看某個(gè)類的所有實(shí)例對象的內(nèi)存地址
heap memory-info.memgraph -addresses all | 'MyDataObject'
代碼中通過malloc_size函數(shù)獲取某對象占用的內(nèi)存大小
malloc_size((__bridge const void *)(object))
leaks
- 查看是否有內(nèi)存泄漏
leaks memory-info.memgraph
- 查看內(nèi)存地址處的泄漏情況
leaks --traceTree [內(nèi)存地址] memory-info.memgraph
lmalloc_history
需要開啟Run->Diagnostics
中的Malloc Stack
功能娜睛,建議使用Live Allocations Only
。則lldb會(huì)記錄debug過程中的對象創(chuàng)建的堆棧卦睹,配合malloc_history
畦戒,即可定位對象的創(chuàng)建過程。
malloc_history memory-info.memgraph [address]
malloc_history memory-info.memgraph --fullStacks [address]
內(nèi)存分配實(shí)例
測試環(huán)境:
iPhone11模擬器+iOS14
macOS Catalina 10.15.7
Xcode 12.1
- malloc 10M內(nèi)存
void *memBlock = malloc(10 * 1024 * 1024);
內(nèi)存類型 | 初始大小 | 當(dāng)前大小 | 增量 |
---|---|---|---|
Virtual | 5364.49M | 5374.50M | +10.01M |
Resident | 119.25M | 119.27M | +0.02M |
Footprint | 18.64M | 18.65M | +0.01M |
僅VM增大10M结序,即只分配了虛擬地址障斋,并映射到物理地址
- memset 10M內(nèi)存
memset(memBlock, 0, 10 * 1024 * 1024);
內(nèi)存類型 | 初始大小 | 當(dāng)前大小 | 增量 |
---|---|---|---|
Virtual | 5374.50M | 5374.50M | 0 |
Resident | 119.27M | 129.27M | +10M |
Footprint | 18.65M | 28.65M | +10M |
訪問虛擬地址,會(huì)觸發(fā)分配物理內(nèi)存頁徐鹤。Resident和Footprint都增加垃环。
- 分配10M虛擬內(nèi)存
vm_address_t address;
vm_size_t size = 100*1024*1024;
vm_allocate((vm_map_t)mach_task_self(), &address, size, VM_MAKE_TAG(200) | VM_FLAGS_ANYWHERE);
內(nèi)存類型 | 初始大小 | 當(dāng)前大小 | 增量 |
---|---|---|---|
Virtual | 5610.42M | 5620.42M | +10M |
Resident | 119.16M | 119.16M | 0 |
Footprint | 18.66M | 18.66M | 0 |
- imageWithFile 加載7.2M圖片
圖片分辨率3502?×?1518,含透明通道返敬,解碼后的Bitmap大小應(yīng)該為20.279M
NSURL* imageUrl = [[NSBundle mainBundle] URLForResource:@"big_pic" withExtension:@"png"];
UIImage* image = [UIImage imageWithContentsOfFile:imageUrl.path];
內(nèi)存類型 | 初始大小 | 當(dāng)前大小 | 增量 |
---|---|---|---|
Virtual | 5661.55M | 5661.55M | 0 |
Resident | 121.67M | 122.96M | +1.29M |
Footprint | 17.68M | 17.76M | +0.08M |
Resident增加了1M左右遂庄,而不是7.2M,因?yàn)槲募成鋬?nèi)存是Clean Memory劲赠,沒必要完全加載到物理內(nèi)存涛目。
- imageWithFile渲染上屏
self.imageView.image = image;
//延遲1s后統(tǒng)計(jì)內(nèi)存,保證渲染結(jié)束
內(nèi)存類型 | 初始大小 | 當(dāng)前大小 | 增量 |
---|---|---|---|
Virtual | 5661.55M | 5830.41M | + 40.22M |
Resident | 122.96M | 164.43M | +41.47M |
Footprint | 17.76M | 58.87M | +41.11M |
Footprint增加的大小約為圖片解碼后的大小的兩倍凛澎,后面會(huì)分析霹肝。
- imageWithFile的Footprint分析
分析:
圖片渲染時(shí),主要增加了兩個(gè)跟解碼大小接近的Dirty內(nèi)存:
20 MB 0 B 1408 KB 26 CoreAnimation
20 MB 0 B 0 B 2 MALLOC_LARGE
所以Resident和Footprint(footprint命令統(tǒng)計(jì))會(huì)增加40M预厌。
退出界面后阿迈,CoreAnimation占用內(nèi)存被釋放, 而MALLOC_LARGE
還存在。
而Instruments的Allocations只捕獲CoreAnimation的分配轧叽,說明MALLOC_LARGE是內(nèi)核分配的苗沧,不在Allocations統(tǒng)計(jì)范圍
但換了個(gè)測試環(huán)境刊棕,結(jié)果卻不一樣了:MALLOC_LARGE
不再增加20M,而是MALLOC_LARGE_REUSABLE
增加了20M的Clean Memory待逞。所以猜想MALLOC_LARGE
也是系統(tǒng)為了復(fù)用而預(yù)分配的內(nèi)存甥角,只是由Clean Memory換成了Dirty Memory,具體意義未知识樱。
iPhone11模擬器+iOS13.3
macOS Catalina 10.15.3
Xcode 11.3
Dirty Clean Reclaimable Regions Category
656 KB 0 B 0 B 9 MALLOC_LARGE
0 B 0 B 20 MB 1 MALLOC_LARGE_REUSABLE
- imageNamed加載7.2M圖片(相同圖片嗤无,不同名字)
UIImage* image = [UIImage imageNamed:@"big_pic2"];
內(nèi)存類型 | 初始大小 | 當(dāng)前大小 | 增量 |
---|---|---|---|
Virtual | 5679.04M | 5679.04M | 0 |
Resident | 122.01M | 123.36M | +0.04M |
Footprint | 17.91M | 18.01M | +0.01M |
- imageNamed渲染上屏
self.imageView2.image = image;
//延遲1s后統(tǒng)計(jì)內(nèi)存,保證渲染結(jié)束
內(nèi)存類型 | 初始大小 | 當(dāng)前大小 | 增量 |
---|---|---|---|
Virtual | 5679.04M | 5868.07M | + 40.59M |
Resident | 123.36M | 184.90M | +40.7M |
Footprint | 18.01M | 58.91M | +20.35M |
- imageNamed的Footprint分析
分析:
圖片渲染時(shí)怜庸,與ImageWithFile的消耗類似当犯,增加了40M Dirty內(nèi)存,但還多了ImageIO產(chǎn)生的20M可回收內(nèi)存(Reclaimable割疾,不計(jì)算到Footprint里)嚎卫,多的這塊其實(shí)是ImageName自動(dòng)解碼產(chǎn)生的:
Dirty Clean Reclaimable Regions Category
--- --- --- --- ---
20 MB 0 B 1408 KB 26 CoreAnimation
20 MB 0 B 0 B 2 MALLOC_LARGE
0 B 0 B 20 MB 1 ImageIO
退出界面時(shí),F(xiàn)ootprint不降反升宏榕,CoreAnimation多消耗了2M拓诸。但再次進(jìn)入同一個(gè)測試頁面,CoreAnimation的消耗回到了20M麻昼〉熘В可見imageNamed
會(huì)將解碼的數(shù)據(jù)緩存在CoreAnimation里,如果圖片較大還是建議使用imageWithFile
參考文章
About the Virtual Memory System
iOS Memory Deep Dive
macOS 內(nèi)核之內(nèi)存占用信息
關(guān)于iOS內(nèi)存的深入排查和優(yōu)化
iOS內(nèi)存分配:虛擬內(nèi)存
iOS 內(nèi)存管理研究
Tips for Allocating Memory
Image and Graphics Best Practices