AutoreleasePool

Objective-C Autorelease Pool 的實(shí)現(xiàn)原理

自動(dòng)釋放池的前世今生 ---- 深入解析 Autoreleasepool

AutoreleasePool是通過一個(gè)以AutoreleasePoolPage為結(jié)點(diǎn)的雙向鏈表來實(shí)現(xiàn)的。AutoreleasePoolPage滿后會(huì)自動(dòng)創(chuàng)建下一個(gè)AutoreleasePoolPage。

Autorelease Pool Blocks

使用clang -rewrite-objc命令將下面的Objective-C代碼重寫成C++代碼:

@autoreleasepool {

}

只保留了部分代碼:

extern "C"?__declspec(dllimport)?void?*?objc_autoreleasePoolPush(void);

extern "C"?__declspec(dllimport)?void objc_autoreleasePoolPop(void?*);

struct __AtAutoreleasePool {

? __AtAutoreleasePool()?{atautoreleasepoolobj?=?objc_autoreleasePoolPush();}

?~__AtAutoreleasePool()?{objc_autoreleasePoolPop(atautoreleasepoolobj);}

void?*?atautoreleasepoolobj;

};

/* @autoreleasepool */?

{

?? ?__AtAutoreleasePool __autoreleasepool;

}

蘋果對(duì)@autoreleasepool{}的實(shí)現(xiàn)真的是非常巧妙,真正可以稱得上藝術(shù)的代碼。蘋果通過一個(gè)__AtAutoreleasePool類型的局部變量 __autoreleasepool來實(shí)現(xiàn)@autoreleasepool{}。當(dāng)聲明__autoreleasepool時(shí),構(gòu)造函數(shù)被執(zhí)行,即執(zhí)行atautoreleasepoolobj?=?objc_autoreleasePoolPush();。當(dāng)出了當(dāng)前作用域時(shí)(即大括號(hào)),析構(gòu)函數(shù)~__AtAutoreleasePool()被執(zhí)行,即調(diào)用用objc_autoreleasePoolPop(atautoreleasepoolobj);。(微信團(tuán)隊(duì)對(duì)特殊字符的處理也用到了相同的技巧)。也就是說@autoreleasepool{}的代碼實(shí)現(xiàn)可以簡(jiǎn)化為如下:

/* @autoreleasepool */?{

void?*atautoreleasepoolobj?=?objc_autoreleasePoolPush();

?//?用戶代碼,所有接收到?autorelease?消息的對(duì)象會(huì)被添加到這個(gè)?autoreleasepool?中

??? objc_autoreleasePoolPop(atautoreleasepoolobj);

}

objc_autoreleasePoolPush和objc_autoreleasePoolPop的實(shí)現(xiàn):

void *objc_autoreleasePoolPush(void) {

?? ?return AutoreleasePoolPage::push();

}

void objc_autoreleasePoolPop(void*ctxt) {?

?? ?AutoreleasePoolPage::pop(ctxt);

}

由此可見內(nèi)部其實(shí)調(diào)用的是AutoreleasePoolPage中相關(guān)的方法

那這里的 AutoreleasePoolPage 是什么東西呢?

其實(shí),autoreleasepool 是沒有單獨(dú)的內(nèi)存結(jié)構(gòu)的,它是通過以 AutoreleasePoolPage 為結(jié)點(diǎn)的雙向鏈表來實(shí)現(xiàn)的。

一個(gè)空的AutoreleasePool的內(nèi)存結(jié)構(gòu)如下:


1. magic 用來校驗(yàn) AutoreleasePoolPage 的結(jié)構(gòu)是否完整;
2. next 指向最新添加的 autoreleased 對(duì)象的下一個(gè)位置,初始化時(shí)指向 begin() ;
3. thread 指向當(dāng)前線程;
4. parent 指向父結(jié)點(diǎn),第一個(gè)結(jié)點(diǎn)的 parent 值為 nil ;
5. child 指向子結(jié)點(diǎn),最后一個(gè)結(jié)點(diǎn)的 child 值為 nil ;
6. depth 代表深度,從 0 開始,往后遞增 1;
7. hiwat 代表 high water mark 。

當(dāng)next==begin()時(shí),表示AutoreleasePage為空,當(dāng)next==end()時(shí)表示AutoreleasePage已滿。每一個(gè)AutoreleasePage的大小都是4096字節(jié)

。我們打開 runtime 的源碼工程,在?NSObject.mm?文件的第 438-932 行可以找到 autoreleasepool 的實(shí)現(xiàn)源碼。

通過閱讀源碼,我們可以知道:

每一個(gè)線程的 autoreleasepool 其實(shí)就是一個(gè)指針的堆棧;

每一個(gè)指針代表一個(gè)需要 release 的對(duì)象或者 POOL_SENTINEL(哨兵對(duì)象,代表一個(gè) autoreleasepool 的邊界);

一個(gè) pool token 就是這個(gè) pool 所對(duì)應(yīng)的 POOL_SENTINEL 的內(nèi)存地址。當(dāng)這個(gè) pool 被 pop 的時(shí)候,所有內(nèi)存地址在 pool token 之后的對(duì)象都會(huì)被 release ;

這個(gè)堆棧被劃分成了一個(gè)以 page 為結(jié)點(diǎn)的雙向鏈表。pages 會(huì)在必要的時(shí)候動(dòng)態(tài)地增加或刪除;

Thread-local storage(線程局部存儲(chǔ))指向 hot page ,即最新添加的 autoreleased 對(duì)象所在的那個(gè) page 。

雙向鏈表:


push操作

?? ?其實(shí)是創(chuàng)建一個(gè)新的AutoreleasePool,對(duì)應(yīng)AutoreleasePoolPage就是在AutoreleasePoolPage的next位置插入一個(gè)POOL_SENTINEL指針(哨兵指針)。next移動(dòng)至下一個(gè)地址。POOL_SENTINEL作為push函數(shù)的返回值。

第一次調(diào)用push操作就會(huì)創(chuàng)建一個(gè)新的autorelease pool。即往AutoreleasePoolPage中插入一個(gè)POOL_SENTINEL,并且返回插入的POOL_SENTINEL的內(nèi)存地址。

autorelease操作

其實(shí)就是將自己的地址插入AutoreleasePoolPage。

[obj autorelease],NSObject中-autorelease方法的實(shí)現(xiàn):

-?(id)autorelease?{

return?((id)self)->rootAutorelease();

}

通過查看((id)self)->rootAutorelease()的方法調(diào)用,發(fā)現(xiàn)調(diào)用的其實(shí)是AutoreleasePoolPage的autorelease函數(shù)

__attribute__((noinline,used))

id

objc_object::rootAutorelease2()

{

??? assert(!isTaggedPointer());

??? return AutoreleasePoolPage::autorelease((id)this);

}

?AutoreleasePoolPage的autorelease函數(shù),它跟push操作非常相似。只不過push操作插入的是一個(gè)POOL_SENTINEL,而autorelease操作插入的是一個(gè)具體的autoreleased對(duì)象(指針)。

static?inline?id?autorelease(id obj)

{

??? assert(obj);

??? assert(!obj->isTaggedPointer());

id?*dest __unused?=?autoreleaseFast(obj);

??? assert(!dest?||?*dest?==?obj);

??? return obj;

}

Pop操作

同理,前面提到的objc_autoreleasePoolPop(void *)?函數(shù)本質(zhì)上也是調(diào)用的AutoreleasePoolPage的pop函數(shù)。

void?objc_autoreleasePoolPop(void?*ctxt)

{

if?(UseGC)?return;

?// fixme?rdar://9167170

if?(!ctxt)?return;

??? AutoreleasePoolPage::pop(ctxt);

}

pop函數(shù)的參數(shù)就是push的返回值,也就是POOL_SENTINEL。當(dāng)執(zhí)行pop操作時(shí),內(nèi)存地址POOL_SENTINEL之后的對(duì)象都會(huì)被release。直到POOL_SENTINEL所在page的next指向POOL_SENTINEL為止。

下圖是某個(gè)線程的AutoreleasePool堆棧的內(nèi)存結(jié)構(gòu)圖,在這個(gè)堆棧中有兩個(gè)POOL_SENTINEL,也就是表示有兩個(gè)autoreleasepool。該堆棧由三個(gè)AutoreleasePoolPage節(jié)點(diǎn)組成,第一個(gè)AutoreleasePoolPage為ColdPage(),最后一個(gè)為HotPage()。其中前兩個(gè)AutoreleasePoolPage已經(jīng)滿了,最后一個(gè)結(jié)點(diǎn)保存了最新添加的autoreleased對(duì)象objr3的內(nèi)存地址。


如果執(zhí)行pop(token1)操作,那么該堆棧的內(nèi)存結(jié)構(gòu)將會(huì)變成如下圖所示:


第二個(gè)POOL_SENTINEL指針之后的對(duì)象調(diào)用release方法,內(nèi)存得到釋放。

什么情況下需要手動(dòng)添加AutoreleasePool:

Each thread in a Cocoa application maintains its own stack of autorelease pool blocks.?

If you are writing a Foundation-only program or if you detach a thread, you need to create your own autorelease pool block.

If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows. If your detached thread does not make Cocoa calls, you do not need to use an autorelease pool block.

1.有大量的零時(shí)對(duì)象,例如編寫的循環(huán)中創(chuàng)建了大量的零時(shí)對(duì)象

2.編寫的的程序不是基于UI框架的,比如說命令行工具

3.創(chuàng)建一個(gè)輔助線程

AutoreleasePool的創(chuàng)建和銷毀

即將進(jìn)入Runloop的時(shí)候創(chuàng)建AutoreleasePool。

RunLoop即將進(jìn)入休眠的時(shí)候釋放并創(chuàng)建新的Runloop。

即將退出Runloop的時(shí)候釋放自動(dòng)釋放池。

App啟動(dòng)后,蘋果在主線程?RunLoop?里注冊(cè)了兩個(gè)?Observer,其回調(diào)都是?_wrapRunLoopWithAutoreleasePoolHandler()。

第一個(gè)?Observer?監(jiān)視的事件是?Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會(huì)調(diào)用?_objc_autoreleasePoolPush()創(chuàng)建自動(dòng)釋放池。其?order?是-2147483647,優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。

第二個(gè)?Observer?監(jiān)視了兩個(gè)事件:?BeforeWaiting(準(zhǔn)備進(jìn)入休眠)時(shí)調(diào)用_objc_autoreleasePoolPop()和?_objc_autoreleasePoolPush()釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop)時(shí)調(diào)用_objc_autoreleasePoolPop()來釋放自動(dòng)釋放池。這個(gè)?Observer?的?order?是?2147483647,優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。

在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)、Timer回調(diào)內(nèi)的。這些回調(diào)會(huì)被?RunLoop?創(chuàng)建好的?AutoreleasePool?環(huán)繞著,所以不會(huì)出現(xiàn)內(nèi)存泄漏,開發(fā)者也不必顯示創(chuàng)建?Pool?了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容