iOS內(nèi)存(Heap堆內(nèi)存 && Anonymous VM 虛擬內(nèi)存) 分析和理解

在使用Instruments 做內(nèi)存分析的時(shí)候, 我們會(huì)看到如下的畫面,箭頭指向的地方有堆內(nèi)存heap Allocations,和虛擬內(nèi)存 Anonymous VM , 到底在手機(jī)上什么是堆內(nèi)存,什么是虛擬內(nèi)存 Anonymous VM 呢? 在觀察內(nèi)存分配的時(shí)候 我們是否需要
去了解它

前言所需要的圖片(如下圖)

image.png

1) 什么是堆

堆是一種完全結(jié)構(gòu)的二叉樹 堆與二叉樹的理解

堆: 是大家共有的空間,分全局堆和局部堆。全局堆就是所有沒有分配的空間,局部堆就是用戶分配的空間。堆在操作系統(tǒng)對(duì)進(jìn)程 初始化的時(shí)候分配,運(yùn)行過程中也可以向系統(tǒng)要額外的堆,但是記得用完了要還給操作系統(tǒng),要不然就是內(nèi)存泄漏。堆里面一般 放的是靜態(tài)數(shù)據(jù),比如static的數(shù)據(jù)和字符串常量等,資源加載后一般也放在堆里面。一個(gè)進(jìn)程的所有線程共有這些堆 ,所以對(duì)堆的操作要考慮同步和互斥的問題。程序里面編譯后的數(shù)據(jù)段都是堆的一部分。

— 一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表

{
int b; //棧
char s[] = "abc"; //棧
char *p2; //棧
char *p3 = "123456"; //123456{row.content}在常量區(qū),p3在棧上。
static int c =0; //全局(靜態(tài))初始化區(qū)
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);//分配得來的10和20字節(jié)的區(qū)域就在堆區(qū)。
strcpy(p1, "123456"); //123456{row.content}放在常量區(qū),編譯器可能會(huì)將它與p3所指向的"123456"優(yōu)化成一個(gè)地方。}

堆:首 先應(yīng)該知道操作系統(tǒng)有一個(gè)記錄空閑內(nèi)存地址的鏈表,當(dāng)系統(tǒng)收到程序的申請時(shí),會(huì)遍歷該鏈表,尋找第一個(gè)空間大于所申請空間的堆結(jié)點(diǎn),然后將該結(jié)點(diǎn)從空閑結(jié) 點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序,另外,對(duì)于大多數(shù)系統(tǒng),會(huì)在這塊內(nèi)存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才 能正確的釋放本內(nèi)存空間。另外,由于找到的堆結(jié)點(diǎn)的大小不一定正好等于申請的大小,系統(tǒng)會(huì)自動(dòng)的將多余的那部分重新放入空閑鏈表中。

堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲(chǔ)的空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見,堆獲得的空間比較靈活,也比較大。碎片問題:對(duì)于堆來講,頻繁的new/delete勢必會(huì)造成內(nèi)存空間的不連續(xù),從而造成大量的碎片,使程序效率降低。對(duì)于棧來講,則不會(huì)存在這個(gè)問題,因?yàn)闂J窍冗M(jìn)后出的隊(duì)列,他們是如此的一一對(duì)應(yīng),以至于永遠(yuǎn)都不可能有一個(gè)內(nèi)存塊從棧中間彈出分配方式:堆都是動(dòng)態(tài)分配的,沒有靜態(tài)分配的堆。

1.1) 堆上消耗的內(nèi)存

image.png
  • View 函數(shù)的調(diào)用
  • 注冊通知
  • 拋出通知
  • view 的布局
  • 函數(shù)代碼的執(zhí)行
  • sqlite 數(shù)據(jù)庫的創(chuàng)建
  • 向字典中增加對(duì)象
    image.png
  • 等等
    都需要消耗內(nèi)存, 上面的代碼都是程序員創(chuàng)建的, 程序員去控制堆的內(nèi)存

1.2) 堆上的內(nèi)存是否釋放

1.2.1) 已經(jīng)釋放的例子:

點(diǎn)擊步驟1)箭頭

image.png

查看步驟2)箭頭

image.png

步驟2) 箭頭中有 free 函數(shù), 可以看出, 這個(gè)對(duì)象 已經(jīng)被釋放

1.2.2) 堆上內(nèi)存不釋放的例子:

上圖中箭頭執(zhí)行的地方 沒有free 函數(shù) 說明 這個(gè)對(duì)象已經(jīng)釋放

2) Anonymous VM

2.1) 蘋果官方文檔對(duì)虛擬內(nèi)存的解釋

  • 更小的內(nèi)存消耗不僅可以減少內(nèi)存, 還可以減少cpu 的時(shí)間

我們可能會(huì)看到這樣的情況, All Heap Allocations 是程序真實(shí)的內(nèi)存分配情況,All Anonymous VM則是系統(tǒng)為程序分配的虛擬內(nèi)存,為的就是當(dāng)程序有需要的時(shí)候,能夠及時(shí)為程序提供足夠的內(nèi)存空間,而不會(huì)現(xiàn)用現(xiàn)創(chuàng)建

Anonymous VM內(nèi)存是虛擬內(nèi)存
、All Anonymous VM。我們無法控制Anonymous VM部分 ,(更新,其實(shí)還是可以優(yōu)化 比如圖片繪制相關(guān) 詳情參見iOS內(nèi)存探究,需要對(duì)虛擬內(nèi)存熟悉 才能優(yōu)化)

2.2) 問題: 我們需要關(guān)注Anonymous VM 內(nèi)存嗎 ?

問答連接
Should you focus on the Live Bytes column for heap allocations or anonymous VM? Focus on the heap allocations because your app has more control over heap allocations. Most of the memory allocations your app makes are heap allocations.

The VM in anonymous VM stands for virtual memory. When your app launches, the operating system reserves a block of virtual memory for your application. This block is usually much larger than the amount of memory your app needs. When your app allocates memory, the operating system allocates the memory from the block it reserved.

Remember the second sentence in the previous paragraph. The operating system determines the size of the virtual memory block, not your app. That’s why you should focus on the heap allocations instead of anonymous VM. Your app has no control over the size of the anonymous VM.

2.3) 不需要關(guān)注 Anonymous VM

我們應(yīng)該關(guān)注堆內(nèi)存, 因?yàn)槲覀儗?duì)堆內(nèi)存有更大的掌控, 大部分我們在app的內(nèi)存分配是堆內(nèi)存

VM 在匿名空間中代表的是虛擬內(nèi)存, 當(dāng)你的app啟動(dòng)的時(shí)候, 操作系統(tǒng)為你的應(yīng)用程序分配內(nèi)存, 這個(gè)分配的虛擬內(nèi)存一般比你的app需要的內(nèi)存大很多,

操作系統(tǒng)決定虛擬內(nèi)存的分配, 而不是你的app, 這就是你為什么要集中精力處理堆內(nèi)存, 你的app 對(duì)虛擬內(nèi)存沒有掌控力

2.4) 虛擬內(nèi)存過大 (未解之謎) 如果知道結(jié)果請?jiān)u論留言, 多謝

CGBitmapContextCreateImage 函數(shù)會(huì)導(dǎo)致虛擬內(nèi)存過大 ,并且還不釋放, 用法未發(fā)現(xiàn)問題

    CGImageRef alphaMaskImage = CGBitmapContextCreateImage(alphaOnlyContext);
    UIImage *result = [UIImage imageWithCGImage:alphaMaskImage scale:image.scale orientation:image.imageOrientation];
    CGImageRelease(alphaMaskImage);
    CGContextRelease(alphaOnlyContext);
    return result;

參考文獻(xiàn)

iOS中的堆(heap)和棧(stack)的理解
蘋果虛擬內(nèi)存的官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容