虛擬內(nèi)存的來由
一個系統(tǒng)中的進(jìn)程是與其他進(jìn)程共享CPU和主存資源的镀梭,最開始我們直接訪問物理內(nèi)存地址,但是后來我們發(fā)現(xiàn)會造成各種各樣的問題:
- 地址空間不隔離
所有的進(jìn)程都可以直接訪問物理地址踱启,那表明各個進(jìn)程的內(nèi)存空間不是互相隔離的报账。有些惡意的進(jìn)程或者被注入惡意代碼的進(jìn)程非常容易去改寫其他進(jìn)程的內(nèi)存數(shù)據(jù),以達(dá)到破壞的目的埠偿。- 內(nèi)存使用效率低
由于沒有有效的內(nèi)存管理機(jī)制透罢,需要一個程序執(zhí)行時,會將整個程序裝入內(nèi)存中然后開始執(zhí)行冠蒋。如果我們這個時候突然想要運行另外一個程序羽圃,那么很可能遇到內(nèi)存空間不足。這時候有一種處理方法是將其他程序的數(shù)據(jù)暫時寫到磁盤里面抖剿,等到用的時候再讀回來朽寞。由于程序所需要的空間是連續(xù)的,那么在這個方法里斩郎,如果我們將程序A換出到磁盤所釋放的內(nèi)存空間是不夠的脑融,所以接著會將程序B換出到磁盤,然后將程序C讀入到內(nèi)存開始運行缩宜。我們可以看出來肘迎,整個過程中有大量的數(shù)據(jù)在換入換出,導(dǎo)致效率十分低下脓恕。- 程序運行的地址不確定
因為程序每次需要裝入運行時,我們需要給它從內(nèi)存中分配一塊足夠大的空閑區(qū)域窿侈,這個空閑區(qū)域的位置是不確定的炼幔。這給程序員的編寫造成了一定的麻煩,因為程序在編寫時史简,它訪問數(shù)據(jù)和指令跳轉(zhuǎn)的目標(biāo)地址很多都是固定的乃秀,需要重定向。
這時候圆兵,就產(chǎn)生了一種解決方案跺讯,一種對主存的抽象概念,叫做 虛擬內(nèi)存(Virtual Memory/VM殉农,下文中為了簡便可能會使用縮寫) 刀脏。
虛擬內(nèi)存的作用
虛擬內(nèi)存是硬件異常、硬件地址翻譯超凳、主存愈污、磁盤文件和內(nèi)核軟件的完美交互耀态,它為每個進(jìn)程提供了一個大的、一致的和私有的地址空間暂雹。
虛擬內(nèi)存提供可三個重要的功能:
- 它將主存看成是一個存儲在磁盤上的地址空間的高速緩存首装,在主存中只保存活動區(qū)域,并根據(jù)需要在磁盤和主存之間來回傳送數(shù)據(jù)杭跪;
- 它為每個進(jìn)程提供了一致的地址空間仙逻,從而簡化了內(nèi)存管理;
- 它保護(hù)了每個進(jìn)程的地址空間不被其他進(jìn)程破壞涧尿。
VM是沉默的工作系奉,不需要開發(fā)人員的任何干涉。但是现斋,我們依然要注意它喜最,原因有三:
- 虛擬內(nèi)存是核心的
VM遍及計算機(jī)系統(tǒng)的所有層面,在硬件異常庄蹋、匯編器瞬内、鏈接器、加載器限书、共享對象虫蝶、文件和進(jìn)程的設(shè)計中扮演著重要的角色。理解VM將幫助開發(fā)者更好的理解系統(tǒng)通常是如何工作的倦西。(尤其是在iOS開發(fā)中D苷妗)- 虛擬內(nèi)存是強(qiáng)大的
VM給予了應(yīng)用程序強(qiáng)大的能力,可以創(chuàng)建和銷毀內(nèi)存片扰柠、將內(nèi)存片映射到磁盤文件中的某個部分(mmap)粉铐,以及與其他進(jìn)程共享內(nèi)存。理解VM將幫助你利用它的強(qiáng)大功能在應(yīng)用程序中添加動力卤档。- 虛擬內(nèi)存是危險的
每次應(yīng)用程序引用一個變量蝙泼、間接引用一個指針,或者調(diào)用一個諸如malloc這樣的動態(tài)分配程序時劝枣,它就會和VM發(fā)生交互汤踏。如果VM使用不當(dāng),應(yīng)用將遇到復(fù)雜危險的與內(nèi)存有關(guān)的錯誤舔腾。理解VM可以幫助開發(fā)者規(guī)避這種錯誤溪胶。
尋址方式
計算機(jī)系統(tǒng)的主存被組織成一個由M個連續(xù)的字節(jié)大小的單元組成的數(shù)組。每個字節(jié)都有一個唯一的物理地址(Physical Address)稳诚。第一個字節(jié)的地址為0哗脖,下一個為1,在往下是2,以此類推懒熙。直接通過物理地址訪問內(nèi)存的方法就是 物理尋址丘损。原理 如下圖:
而現(xiàn)在除了嵌入式設(shè)備和某些超級計算機(jī)意外,我們使用 虛擬尋址來取代物理尋址工扎。
使用虛擬尋址徘钥,CPU通過生成一個虛擬地址(VIrtual Address)來訪問主存,這個虛擬地址在被送到內(nèi)存前先轉(zhuǎn)換成適當(dāng)?shù)奈锢淼刂分铩⑻摂M地址轉(zhuǎn)換成物理地址的任務(wù)叫做地址翻譯呈础。原理 如下圖:
注:MMU(Memory Management Unit,內(nèi)存管理單元)橱健,CPU上的一個專用硬件澳淑,用來存放在主存中的查詢表來動態(tài)翻譯虛擬地址绑青。
地址空間
地址空間是一個線性的非負(fù)整數(shù)地址的有序集合:
如果像是{0,1,2,……}一樣,我們可以稱之為線性地址空間。
分為虛擬地址空間和物理地址空間蜀细,分別對應(yīng)虛擬內(nèi)存和物理內(nèi)存爽彤。
地址空間幫助我們區(qū)分了數(shù)據(jù)對象(字節(jié))和它們的屬性(地址)溢十。主存中的每字節(jié)都有一個選自虛擬空間的虛擬地址和一個選自物理空間的物理地址懊缺。
頁
(注意,這里只講了頁式虛擬內(nèi)存蟋定,還有另外一種段式虛擬內(nèi)存粉臊,也可以把頁式當(dāng)成一種特殊的段式)
現(xiàn)代操作系統(tǒng)將內(nèi)存劃分為頁,來簡化內(nèi)存管理驶兜,一個頁其實就是一段連續(xù)的內(nèi)存地址的集合扼仲,通常有 4k 和 16k(iOS 64 位是 16K)的,成為 Virtual Page 虛擬頁抄淑。與之對應(yīng)的物理內(nèi)存被稱為 Physical Page 物理頁屠凶。
注意 虛擬頁的個數(shù)可能和物理頁個數(shù)不一樣 比如說一個 64 位操作系統(tǒng)中使用 48 位地址空間的 虛擬頁大小為 16K,那么其虛擬頁可數(shù)可達(dá)到(2^48 / 2^14 = 16M 個)假設(shè)物理內(nèi)存只有 4G 那么物理頁可能只有 (2^32 / 2^14 = 256k 個)肆资。
任何時刻矗愧,虛擬頁面的集合都分為三個不想交的子集:
- 未分配的:VM系統(tǒng)還未分配的(或者創(chuàng)建)的頁。未分配的塊沒有任何數(shù)據(jù)和它們相關(guān)聯(lián)迅耘,因此也就不占用任何磁盤空間贱枣。
- 緩存的:當(dāng)前已緩存在物理內(nèi)存中的已分配頁监署。
- 未緩存的:未緩存在物理內(nèi)存中的已分配頁颤专。
DRAM中的結(jié)構(gòu)
我們用SRAM(靜態(tài)RAM)來表示L1、L2和L3高速緩存钠乏,用DRAM(動態(tài)RAM)表示虛擬內(nèi)存中的緩存(它在主頁中緩存虛擬頁)栖秕。
在緩存中,DRAM未命中比SRAM要昂貴的多晓避,因為SRAM未命中可以有DRAM來兜底簇捍,DRAM未命中就需要用磁盤來兜底(磁盤要比DRAM慢100000多倍只壳,而且從磁盤的一個扇區(qū)讀取第一個字節(jié)的時間開銷比讀連續(xù)的字節(jié)要慢非常非常多)。因為上面的原因暑塑,虛擬頁往往設(shè)置的比較大吼句,通常4KB-2MB。而且事格,DRAM是全相聯(lián)的惕艳, 任何虛擬頁都可以放置在任何物理頁之中。
頁表
操作系統(tǒng)使用頁表(PageTable)驹愚,將虛擬頁映射到物理頁远搪。每次地址翻譯硬件將一個虛擬地址轉(zhuǎn)換為物理地址時,都會讀取頁表逢捺。
頁表實際上是一個頁表條目(Page Table Entry谁鳍,PTE)的數(shù)組。虛擬地址空間中的每個頁在頁表中一個固定偏移處都有一個PTE劫瞳。結(jié)構(gòu)如下:
缺頁
假如DRAM緩存未命中倘潜,被稱之為缺頁(page fault)。當(dāng)缺頁發(fā)生時柠新,會啟動內(nèi)核中的缺頁異常程序窍荧,選擇一個犧牲頁,進(jìn)行磁盤和內(nèi)存中數(shù)據(jù)的交換恨憎。
在iOS系統(tǒng)中蕊退,因為內(nèi)存的緊張,并未采用這種方式憔恳,而是類似OOM警告的方式來控制瓤荔,MacOS中是存在的。
虛擬內(nèi)存的內(nèi)存管理
VM為每個進(jìn)程都提供了一個獨立的頁表钥组,因而也就是一個獨立的虛擬地址空間输硝。使用VM也會有很多優(yōu)點
- 簡化鏈接
每個獨立的地址空間允許每個進(jìn)程的內(nèi)存映像使用相同的基本格式,而不管代碼和數(shù)據(jù)實際存放在物理內(nèi)存的何處程梦。- 簡化加載
VM使得容易向內(nèi)存中加載可執(zhí)行文件和共享對象文件点把。- 簡化共享
獨立的地址空間為操作系統(tǒng)提供了一個管理用戶進(jìn)程和操作系統(tǒng)自身之間共享的一致機(jī)制。- 簡化內(nèi)存分配
假如遇到需要共享內(nèi)存數(shù)據(jù)的時候屿附,VM機(jī)制可以幫助我們有選擇的訪問共享頁面郎逃。
內(nèi)存保護(hù)
我們應(yīng)該明白,不應(yīng)該允許一個用戶進(jìn)程任意修改它的只讀代碼段挺份;不允許修改內(nèi)核的代碼和數(shù)據(jù)結(jié)構(gòu)褒翰;不允許讀寫其他進(jìn)程的私有內(nèi)存。
為了提供這種保護(hù),地址翻譯機(jī)制會在讀取PTE的時候优训,添加一些額外的許可位來控制虛擬頁面朵你。如下圖所示:
地址翻譯
n位的虛擬地址包含兩個部分:p位的虛擬頁面偏移(VPO)和一個(n-p)位的虛擬頁號(VPN)。MMU使用VPN來選擇適當(dāng)?shù)腜TE揣非。
這里詳細(xì)細(xì)節(jié)查看《深入理解計算機(jī)系統(tǒng)》(第三版)p568-p570
TLB
每次CPU產(chǎn)生一個地址抡医,MMU就必須查閱一個PTE,以便將虛擬地址翻譯成為物理地址早敬。這會造成非常大的性能損耗魂拦。如何解決呢?答案簡單搁嗓,使用緩存芯勘!我們在這里使用一個叫做 翻譯后備緩沖器(Transalation Lookaside Buffer-TLB)的東西來幫助我們處理。當(dāng)TLB未命中時腺逛,MMU再去L1緩存中獲取對應(yīng)的PTE荷愕,然后再將它放到TLB中。
多級頁表
一般來說棍矛,系統(tǒng)的地址空間也是有限的安疗,我們不能每次都要一起訪問整個頁表。這里我們可以使用 多級頁表技術(shù)够委。
一級頁表對應(yīng)二級頁表荐类,二級頁表對應(yīng)虛擬內(nèi)存頁面。我們只要把一級頁表一直放到主存中就好了茁帽,需要的時候再去訪問二級頁表玉罐。
Linux中的虛擬內(nèi)存
操作系統(tǒng)為每個進(jìn)程維護(hù)一個單獨的虛擬地址空間,分為兩部分潘拨。
- 內(nèi)核虛擬內(nèi)存
包含內(nèi)核中的代碼和數(shù)據(jù)結(jié)構(gòu)吊输,還有一些被映射到所有進(jìn)程共享的內(nèi)存頁面。還有一些頁表铁追,內(nèi)核在進(jìn)程上下文中執(zhí)行代碼使用的棧季蚂。- 進(jìn)程虛擬內(nèi)存
OS 將內(nèi)存組織為一些區(qū)域(Sement)的集合,代碼端琅束,數(shù)據(jù)端扭屁,共享庫端,線程棧都是不同的區(qū)域涩禀,分段的原因是便于管理內(nèi)存的權(quán)限料滥,如果了解過 Mach-O 文件或者 ELF 文件的讀者可以看到相同的 Segment 里面的內(nèi)存權(quán)限是相同的,每個 Segment 再劃分不同的內(nèi)容為 section埋泵。
內(nèi)存映射 (mmap)
在Linux中幔欧,通過將一個虛擬內(nèi)存區(qū)域與一個磁盤上的對象關(guān)聯(lián)起來,以初始化這個虛擬內(nèi)存區(qū)域的內(nèi)容丽声。
大致過程如下:進(jìn)程先在虛擬地址空間中創(chuàng)建虛擬映射區(qū)域礁蔗,然后內(nèi)核開始調(diào)用mmap函數(shù),實現(xiàn)物理地址和虛擬地址的映射雁社。
實現(xiàn)細(xì)節(jié)可以查看 《深入理解計算機(jī)系統(tǒng)》(第三版)p582-p586
我們需要記自【:mmap為共享書、創(chuàng)建新的進(jìn)程以及加載程序提供了一個高效的機(jī)制霉撵。應(yīng)用可以使用mmap函數(shù)來手工德創(chuàng)建和刪除虛擬地址空間區(qū)域內(nèi)一個稱謂堆(heap)的區(qū)域磺浙。
而mmap在iOS的用處:
- mmap 讓讀寫一個文件像操作一個內(nèi)存地址一樣簡單方便,
- mmap 效率極高徒坡,不用將一個內(nèi)容從磁盤讀入內(nèi)核態(tài)再拷貝至用戶態(tài)
- mmap映射的文件由操作系統(tǒng)接管撕氧,如果進(jìn)程 Crash 操作系統(tǒng)會保證文件刷新回磁盤。
通過以上的特點喇完,我們可以在圖片加載(例如FastImageCache)伦泥,數(shù)據(jù)存儲以及關(guān)鍵的crash收集上報中使用。
動態(tài)內(nèi)存分配
在運行時需要額外的虛擬內(nèi)存的時候锦溪,用動態(tài)內(nèi)存分配器更方便不脯、更好的可移植性。
動態(tài)內(nèi)存分配器維護(hù)著一個進(jìn)程的虛擬內(nèi)存區(qū)域刻诊,稱之為 堆防楷。堆可以被視為一組大小不同的塊(block)的集合。這些塊要不然就是分配的则涯,要不然就是空閑的复局。
分配器有兩種基本風(fēng)格,兩種風(fēng)格都要求顯式的分配塊:
- 顯式分配器 (手動管理內(nèi)存,嚴(yán)格來講ARC算是這個的變種)
- 隱式分配器 (垃圾收集粟判,Java等語言采用這種)
顯示分配器的實現(xiàn)細(xì)節(jié)可以查看 《深入理解計算機(jī)系統(tǒng)》(第三版)p587-p605肖揣。十分推薦iOS去讀,很多時候跳出來看一下原理浮入,會讓自己有新的認(rèn)知龙优。
隱式分配器或者說垃圾收集實現(xiàn)細(xì)節(jié)可以查看 《深入理解計算機(jī)系統(tǒng)》(第三版)p605-p609。
因為我對使用GC的語言的語言沒什么研究事秀,兩者的區(qū)別優(yōu)劣我無法給出彤断,不過推薦一下這篇文章Garbage Collection vs Automatic Reference Counting。
傾寒推薦這個代碼C + + 實現(xiàn)一個簡易的內(nèi)存池分配器易迹,也可以看一下宰衙。
iOS Memory
在上邊我們了解的頁這一概念,iOS實際上也是使用了這一概念睹欲。
我們使用以下代碼來查看數(shù)據(jù)
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "mach/mach.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
printf("page-size:%ld \nmask:%ld\nshift:%d \n", vm_kernel_page_size, vm_kernel_page_mask, vm_kernel_page_shift);
printf("%ld\n", sysconf(_SC_PAGE_SIZE));
printf("%d\n", getpagesize());
printf("%lu\n", PAGE_SIZE);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
得到的數(shù)據(jù)為(iPhone 8 plus和iPhone SE):
page-size:16384
mask:16383
shift:14
16384
16384
16384 = 16kb
我們用過以上數(shù)據(jù)可以得知:頁大小為16kb供炼,虛擬地址偏移為14一屋。這里即使用上文中提過的TLB來翻譯地址。
在我們未寫入數(shù)據(jù)袋哼,但是剛剛被操作系統(tǒng)分配或者磁盤映射的時候冀墨,內(nèi)存都是出于 Clean的狀態(tài),但是一旦被寫入了沒操作系統(tǒng)會將它標(biāo)記為 Dirty涛贯。
- Clean:指的是能夠被系統(tǒng)清理出內(nèi)存并且在有需要的時候重新加載的數(shù)據(jù)诽嘉,包括:Memory mapped files;Frameworks中的__DATA_CONST部分弟翘;應(yīng)用的二進(jìn)制可執(zhí)行文件虫腋。
- Dirty:指的是不能被系統(tǒng)回收的內(nèi)存占用,包括:堆上的對象稀余;圖片解碼數(shù)據(jù)悦冀;Frameworks中的__DATA和__DATA_DIRTY部分。
標(biāo)記的一個好處在于:因為Dirty頁面已經(jīng)被寫入數(shù)據(jù)睛琳,是要比Clean重要的多的雏门。當(dāng)操作系統(tǒng)發(fā)現(xiàn)內(nèi)存十分緊張的時候,會嘗試驅(qū)逐一部分內(nèi)存頁面掸掏。Clean的頁面會因為優(yōu)先級的原因被首先驅(qū)逐茁影,并開始和磁盤(中的backing store部分)交換分區(qū),等到需要使用的時候再去讀取丧凤。
但是我們這里要注意一點募闲,iOS因為是在移動端使用,移動端使用的是閃存≡复現(xiàn)在新版的iPhone使用的都是TLC(Triple-Level Cell),過于頻繁的讀寫會嚴(yán)重影響閃存的使用壽命(實際上是二氧化硅薄膜因為電子的頻繁進(jìn)出而變焙坡荨)。所以并沒有使用上邊這個磁盤交換機(jī)制仍侥,因此如果出現(xiàn)內(nèi)存緊張的情況要出,iOS會使用Compressed Memory機(jī)制。在內(nèi)存緊張的時候农渊,將不常使用的內(nèi)存壓縮并且在需要的時候解壓患蹂。
When your system’s memory begins to fill up, Compressed Memory automatically compresses the least recently used items in memory, compacting them to about half their original size. When these items are needed again, they can be instantly uncompressed.
這個舉措,特點可以歸納為:
- 減少了不活躍內(nèi)存占用
- 改善了電源效率砸紊,通過壓縮減少磁盤IO帶來的損耗
- 壓縮/解壓十分迅速传于,能夠盡可能減少 CPU 的時間開銷
- 支持多核操作
從某種意義上來說,我們可以把Compressed memory視為Dirty memory醉顽。
memory footprint = dirty size + compressed size 沼溜,這也就是我們需要并且能夠嘗試去減少的內(nèi)存占用
當(dāng)我們的app的memory footprint 達(dá)到一定的值時,我們會受到內(nèi)存警告(Memory Warnings)游添。
如果我們收到了內(nèi)存警告系草,系統(tǒng)本身會釋放一部分內(nèi)存頁面(例如NSCache機(jī)制)通熄,但是也會向當(dāng)前運行的程序發(fā)送低內(nèi)存警告,我們也要對此作出相應(yīng)找都。
UIKit中有幾種接受低內(nèi)存警告的方法:
- applicationDidReceiveMemoryWarning:方法唇辨;
- 在UIViewController中重寫didReceiveMemoryWarning;
- 注冊接受UIApplicationDidReceiveMemoryWarningNotification通知
如果我們對此置之不理檐嚣,程序有可能直接被干掉,那時候我們就會陷入OOM的困境之中啰扛。
監(jiān)測內(nèi)存的工具
Xcode
命令行工具暫且不提嚎京,那套更加適合MacOS。
在Xcode中隐解,我們可以使用三種工具來測量內(nèi)存:
- Xcode memory gauge
- Instruments(主要是Leaks鞍帝、Allocation、Counters以及System Trace中的Virtual Memory Trace)
- Xcode Memory Debugger
在Xcode10之后煞茫,當(dāng)內(nèi)存過大的時候帕涌,也會觸發(fā)debugger,自動捕獲 EXC_RESOURCE RESOURCE_TYPE_MEMORY
異常续徽,并自動斷點在出問題的地方蚓曼。
在在Product->Scheme->Edit Scheme->Diagnostics中,開啟 Malloc Stack 功能钦扭,建議使用Live Allocations Only選項纫版。
中開啟Malloc Stack功能,使用 Live Allocations Only選項客情,會在lldb中記錄調(diào)試過程中對象創(chuàng)建的堆棧其弊,配合使用 malloc_history
工具,可以方便我們定位到占用過大內(nèi)存的對象的創(chuàng)建位置膀斋。
代碼方法
獲取應(yīng)用使用真實物理內(nèi)存值的代碼:
- (NSUInteger)getResidentMemory
{
struct mach_task_basic_info info;
mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
int r = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)& info, & count);
if (r == KERN_SUCCESS)
{
return info.resident_size;
}
else
{
return -1;
}
}
線上內(nèi)存檢測工具
1.MLeaksFinder
2.FBRetainCycleDetector
3.OOMDetector
當(dāng)然梭伐,我們也可以自己在理解內(nèi)存檢測的原理之后,自己去實現(xiàn)一些輪子仰担,以更加貼合自己的使用場景糊识。
如何注意內(nèi)存優(yōu)化
0.多用懶加載
1.weak替代 unsafe_unretain ,以及注意assign摔蓝;
2.安全的使用weak技掏;
3.autoreleasepool多用;
4.對UI项鬼、動畫機(jī)制深入了解哑梳,尤其是動畫以及Cell復(fù)用機(jī)制;
5.imageName:
5.performSelect謹(jǐn)慎使用绘盟;
6.倒計時使用注意鸠真,設(shè)計一定要嚴(yán)謹(jǐn)悯仙;
7.多使用Cache而非dictionary;
8.監(jiān)測性能組件使用mmap存放讀取數(shù)據(jù)吠卷;
9.NSDateFormate注意锡垄;
10.謹(jǐn)慎小心的使用指針,小心野指針祭隔;
11.WKWebView 是跨進(jìn)程通信的货岭,不會占用我們的 APP 使用的物理內(nèi)存量,但是依然要小心謹(jǐn)慎的測量疾渴;
12.在保證安全的前提下千贯,選用一些更小的數(shù)據(jù)結(jié)構(gòu);
13.對待大的貼圖要謹(jǐn)慎使用搞坝;
14.謹(jǐn)慎小心的使用指針搔谴;
15.注意 NSDateFormatter的使用。
后續(xù)記錄計劃
SLC/MLC/TLC對比桩撮,為什么選用TLC敦第。
OOM是個什么鬼。
如何設(shè)計一個一個內(nèi)存監(jiān)測組件店量。
如何注意內(nèi)存優(yōu)化
中各項的解釋芜果。
參考和鳴謝
《程序員的自我修養(yǎng)-鏈接、裝載和庫》第一版(第十章)
《深入理解計算機(jī)系統(tǒng)》第三版(第九章)
《iOS 和 macOS 性能優(yōu)化:Cocoa融师、Cocoa Touch师幕、Objective-C 和 Swift》第一版(第五章)
《高性能iOS應(yīng)用開發(fā)》 第一版-第二章
《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法》第五十條
WWDC 2018-Session 416: iOS Memory Deep Dive
iOS Memory Deep Dive
OS X Mavericks Core Technology Overview
Memory Usage Performance Guidelines
Instruments Help
iOS-Monitor-Platform
感謝傾寒、冬瓜在創(chuàng)作中給予的幫助诬滩。
結(jié)語
iOS的APM是真的難霹粥!
寫了這么多越來越覺得自己蠢!L勰瘛:罂亍!?站怠:铺浴!
還是要多看書吴攒!
投簡歷是真的難张抄!