一、Autoreleasepool概念
- 自動釋放池(autorelease pool)是OC的一種內(nèi)存自動回收機制. 具有延遲釋放的特性,即當(dāng)我們創(chuàng)建了一個對象,并把他加入到了自動釋放池中時,他不會立即被釋放,會等到一次runloop結(jié)束或者作用域超出{}或者超出[pool release]之后再被釋放
- 當(dāng)你向一個對象發(fā)送一個autorelease消息的時候,Cocoa就會將對象的一個引用放入
到最新的自動釋放池中(當(dāng)前線程棧頂位置),它仍然是一個對象,因此自動釋放池定義的作用域內(nèi)的其他對象都可以向他發(fā)送消息.一個自動釋放池存儲的對象當(dāng)自己被銷毀的時會向其中的對象發(fā)送 release 消息。
二、Autoreleasepool創(chuàng)建和銷毀時機
MRC
- 通過手動創(chuàng)建的方式來創(chuàng)建自動釋放池,這種方式創(chuàng)建的自動釋放池需要手動調(diào)用release(引用計數(shù)環(huán)境下,調(diào)用release和drain的效果相同,但是在CG下,drain會觸發(fā)GC,release不會),方法如下:
NSAutoreleasePool *pool = [[ NSAutoreleasePool alloc]init ];//創(chuàng)建一個自動釋放池
Person *person = [[Person alloc]init];
//調(diào)autorelease方法將對象加入到自動釋放池
//注意使用該方法,對象不會自己加入到自動釋放池,需要人為調(diào)用autorelease方法加入
[person autorelease];
//,手動釋放自動釋放池執(zhí)行完這行代碼是,自動釋放池會對加入他中的對象做一次release操作
[pool release];
自動釋放池銷毀時機:[pool release]代碼執(zhí)行完后
- 通過@autoreleasepool來創(chuàng)建
- 對象的創(chuàng)建在自動釋放池里面
@autoreleasepool {
//在這個{}之內(nèi)的變量默認被添加到自動釋放池
Person *p = [[Person alloc] init];
}//除了這個括號,p被釋放
- 如果一個變量在自動釋放池之外創(chuàng)建,如下,需要通過__autoreleasing該修飾符將其加入到自動釋放池。
@autoreleasepool {
}
Person * __autoreleasing p = [
[Person alloc]init];
self.person = p;
系統(tǒng)就是通過@autoreleasepool {}這種方式來為我們創(chuàng)建自動釋放池的,一個線程對應(yīng)一個runloop,系統(tǒng)會為每一個runloop隱式的創(chuàng)建一個自動釋放池,所有的autoreleasePool構(gòu)成一個棧式結(jié)構(gòu),在每個runloop結(jié)束時,當(dāng)前棧頂?shù)腶utoreleasePool會被銷毀,而且會對其中的每一個對象做一次release(嚴格來說,是你對這個對象做了幾次autorelease就會做幾次release,不一定是一次),特別指出,使用容器的block版本的枚舉器的時候,系統(tǒng)會自動添加一個autoreleasePool
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 這里被一個局部@autoreleasepool包圍著
}];
ARC
ARC下除了NSAutoreleasePool不可用以外,其他的同MRC
三、Autoreleasepool 應(yīng)用場景
MRC:
- 對象作為函數(shù)返回值
- 當(dāng)一個對象要作為函數(shù)返回值的時候,因為要遵循誰申請誰釋放的思想,所以應(yīng)該在返回之前釋放,但要是返回之前釋放了,就會造成野指針錯誤,但是要是不釋放,那么就違背了誰申請誰釋放的原則,所以就可以使用autorelease延遲釋放的特性,將其在返回之前做一次autorelease,加入到自動釋放池中,保證可以被返回,一次runloop之>>后系統(tǒng)會幫我們釋放他
- 自動釋放池可以延長對象的聲明周期,如果一個事件周期很長,比如有一個很長的循環(huán)邏輯,那么一個臨時變量可能很長時間都不會被釋放,一直在內(nèi)存中保留,那么內(nèi)存的峰值就會一直增加,但是其實這個臨時變量是我們不再需要的。這個時候就通過創(chuàng)建新的自動釋放池來縮短臨時變量的生命周期來降低內(nèi)存的峰值。
- 臨時生成大量對象,一定要將自動釋放池放在for循環(huán)里面,要釋放在外面,就會因為大量對象得不到及時釋放,而造成內(nèi)存緊張,最后程序意外退出
for (int i = 0; i<10000; i++) {
@autoreleasepool {
UIImageView *imegeV = [[UIImageView alloc]init];
imegeV.image = [UIImage imageNamed:@"efef"];
[self.view addSubview:imegeV];
}
}
嵌套的autoreleasepool
@autoreleasepool {//p1被放在該自動釋放池里面
Person *p1 = [[Person alloc]init];
@autoreleasepool {//p2被放在該自動釋放池里面
Person *p2 = [[Person alloc]init];
}
}
以上說明autorelease對象被添加進離他最近的自動釋放池,多層的pool會有多個哨兵對象。
四、Autoreleasepool 底層實現(xiàn)
Autoreleasepool實現(xiàn)原理
在了解自動釋放池的原理之前,我們先來看一下以下幾個概念
AutoreleasePoolPage
- ARC下,我們使用@autoreleasepool{}來使用一個AutoreleasePool,隨后編譯器將其改寫成下面的樣子:
void *context = objc_autoreleasePoolPush();
// {}中的代碼
objc_autoreleasePoolPop(context);
- 而這兩個函數(shù)都是對AutoreleasePoolPage的簡單封裝,所以自動釋放機制的核心就在于這個類。
AutoreleasePoolPage是一個C++實現(xiàn)的類
- AutoreleasePoolPage:每一個自動釋放池沒有單獨的結(jié)構(gòu),每一個autorealeasePool對象都是由若干個autoreleasePoolPage通過雙向鏈表連接而成,類的定義如下
class AutoreleasePoolPage
{
magic_t const magic;
id *next;//指向棧頂最新被添加進來的autorelease對象的下一個位置
pthread_t const thread;//指向當(dāng)前線程
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t
}
- AutoreleasePool是按線程一一對應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線程)
- AutoreleasePoolPage每個對象會開辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁的大小),除了上面的實例變量所占空間,剩下的空間全部用來儲存autorelease對象的地址
- 上面的id *next指針作為游標(biāo)指向棧頂最新add進來的autorelease對象的下一個位置
- 一個AutoreleasePoolPage的空間被占滿時,會新建一個AutoreleasePoolPage對象,連接鏈表,后來的autorelease對象在新的page加入
所以,若當(dāng)前線程中只有一個AutoreleasePoolPage對象,并記錄了很多autorelease對象地址時內(nèi)存如下圖:
圖中的情況,這一頁再加入一個autorelease對象就要滿了(也就是next指針馬上指向棧頂),這時就要執(zhí)行上面說的操作,建立下一頁page對象,與這一頁鏈表連接完成后,新page的next指針被初始化在棧底(begin的位置),然后繼續(xù)向棧頂添加新對象。
所以,向一個對象發(fā)送- autorelease消息,就是將這個對象加入到當(dāng)前AutoreleasePoolPage的棧頂next指針指向的位置
哨兵對象
- 本質(zhì)是一個值為nil的對象,由被定義在* NSAutoreleasePool類中的_token指針來保存。
hotPage
- 最新創(chuàng)建的AutoreleasePoolPage.
原理
系統(tǒng)通過一個棧來管理所有的自動釋放池,每當(dāng)創(chuàng)建了一個新的自動釋放池,系統(tǒng)就會把它壓入棧頂,并且傳入一個哨兵對象,將哨兵對象插入hotPage,這里分三種情況
- 若hotPage未滿,則直接插入哨兵對象,
- 要是滿了,新建一個NSAutoreleasePoolPage,并將其作為hotPage,然后將哨兵對象插入
- 如果沒有NSAutoreleasePoolPage,則新建一個NSAutoreleasePoolPage,并將其作為hotPage,插入哨兵對象,注意。這里的hotPage是沒有父節(jié)點的。
每當(dāng)有一個自動釋放池要被釋放的時候,哨兵對象就會作為參數(shù)被傳入,找到該哨兵對象所在的位置后,將所有晚于哨兵對象的autorelease彈出,并對他們做一次release,然后將next指針一到合適的位置。
Autoreleasepool底層實現(xiàn)
- 首先我們?nèi)タ梢圆榭匆幌拢琧lang 轉(zhuǎn)成 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;
};
可以發(fā)現(xiàn)objc_autoreleasePoolPush() 和 objc_autoreleasePoolPop() 這兩個方法。
- 再看一下runtime 中 Autoreleasepool 的結(jié)構(gòu),通過閱讀源碼可以看出 Autoreleasepool 是一個由 AutoreleasepoolPage 雙向鏈表的結(jié)構(gòu),其中 child 指向它的子 page,parent 指向它的父 page。
- 并且每個 AutoreleasepoolPage 對象的大小都是 4096 個字節(jié)。
#define PAGE_MAX_SIZE PAGE_SIZE
#define PAGE_SIZE I386_PGBYTES
#define I386_PGBYTES 4096 /* bytes per 80386 page */
- AutoreleasepoolPage 通過壓棧的方式來存儲每個需要自動釋放的對象。
//入棧方法
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
// Each autorelease pool starts on a new pool page.
//在 Debug 情況下每一個自動釋放池 都以一個新的 poolPage 開始
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
//正常情況下,調(diào)用 push 方法會先插入一個 POOL_BOUNDARY 標(biāo)志位
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
- 然后我們來看看 runtime 中的源碼
/***********************************************************************
自動釋放池的實現(xiàn):
一個線程的自動釋放池是一個指針堆棧
每一個指針或者指向被釋放的對象,或者是自動釋放池的 POOL_BOUNDARY,POOL_BOUNDARY 是自動釋放池的邊界。
一個池子的 token 是指向池子 POOL_BOUNDARY 的指針。當(dāng)池子被出棧的時候,每一個高于標(biāo)準的對象都會被釋放掉。
堆棧被分成一個頁面的雙向鏈表。頁面按照需要添加或者刪除。
本地線程存放著指向當(dāng)前頁的指針,在這里存放著新創(chuàng)建的自動釋放的對象。
**********************************************************************/
// Set this to 1 to mprotect() autorelease pool contents
//將這個設(shè)為 1 可以通過mprotect修改映射存儲區(qū)的權(quán)限來更改自動釋放池的內(nèi)容
#define PROTECT_AUTORELEASEPOOL 0
#define CHECK_AUTORELEASEPOOL (DEBUG)
BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj));
BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token));
namespace {
//對AutoreleasePoolPage進行完整性校驗
struct magic_t {
static const uint32_t M0 = 0xA1A1A1A1;
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12;
uint32_t m[4];
magic_t() {
assert(M1_len == strlen(M1));
assert(M1_len == 3 * sizeof(m[1]));
m[0] = M0;
strncpy((char *)&m[1], M1, M1_len);
}
~magic_t() {
m[0] = m[1] = m[2] = m[3] = 0;
}
bool check() const {
return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
}
bool fastcheck() const {
#if CHECK_AUTORELEASEPOOL
return check();
#else
return (m[0] == M0);
#endif
}
# undef M1
};
//自動釋放頁
class AutoreleasePoolPage
{
//EMPTY_POOL_PLACEHOLDER 被存放在本地線程存儲中當(dāng)一個池入棧并且沒有存放任何對象的時候。這樣在棧頂入棧出棧并且沒有使用它們的時候會節(jié)省內(nèi)存。
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
# define POOL_BOUNDARY nil
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
//對象數(shù)量
static size_t const COUNT = SIZE / sizeof(id);
//校驗完整性
magic_t const magic;
//頁中對象的下一位索引
id *next;
//線程
pthread_t const thread;
//父頁
AutoreleasePoolPage * const parent;
//子頁
AutoreleasePoolPage *child;
//深度
uint32_t const depth;
uint32_t hiwat;
// SIZE-sizeof(*this) bytes of contents follow
//創(chuàng)建
static void * operator new(size_t size) {
return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
}
//刪除
static void operator delete(void * p) {
return free(p);
}
//設(shè)置當(dāng)前內(nèi)存可讀
inline void protect() {
#if PROTECT_AUTORELEASEPOOL
mprotect(this, SIZE, PROT_READ);
check();
#endif
}
//設(shè)置當(dāng)前內(nèi)存可讀可寫
inline void unprotect() {
#if PROTECT_AUTORELEASEPOOL
check();
mprotect(this, SIZE, PROT_READ | PROT_WRITE);
#endif
}
//初始化
AutoreleasePoolPage(AutoreleasePoolPage *newParent)
: magic(), next(begin()), thread(pthread_self()),
parent(newParent), child(nil),
depth(parent ? 1+parent->depth : 0),
hiwat(parent ? parent->hiwat : 0)
{
if (parent) {
parent->check();
assert(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
//析構(gòu)
~AutoreleasePoolPage()
{
check();
unprotect();
assert(empty());
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
assert(!child);
}
//被破壞的
void busted(bool die = true)
{
magic_t right;
(die ? _objc_fatal : _objc_inform)
("autorelease pool page %p corrupted\n"
" magic 0x%08x 0x%08x 0x%08x 0x%08x\n"
" should be 0x%08x 0x%08x 0x%08x 0x%08x\n"
" pthread %p\n"
" should be %p\n",
this,
magic.m[0], magic.m[1], magic.m[2], magic.m[3],
right.m[0], right.m[1], right.m[2], right.m[3],
this->thread, pthread_self());
}
//校驗
void check(bool die = true)
{
if (!magic.check() || !pthread_equal(thread, pthread_self())) {
busted(die);
}
}
//快速校驗
void fastcheck(bool die = true)
{
#if CHECK_AUTORELEASEPOOL
check(die);
#else
if (! magic.fastcheck()) {
busted(die);
}
#endif
}
//頁的開始位置
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
//頁的結(jié)束位置
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
//頁是否是空的
bool empty() {
return next == begin();
}
//頁是否是滿的
bool full() {
return next == end();
}
//是否少于一半
bool lessThanHalfFull() {
return (next - begin() < (end() - begin()) / 2);
}
//添加對象
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
//釋放所有對象
void releaseAll()
{
releaseUntil(begin());
}
//釋放到 stop 的位置之前的所有對象
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
//將頁索引內(nèi)容置為 SCRIBBLE 表示已經(jīng)被釋放
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
//殺死
void kill()
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
AutoreleasePoolPage *page = this;
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
//釋放本地線程存儲空間
static void tls_dealloc(void *p)
{
if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
// No objects or pool pages to clean up here.
return;
}
// reinstate TLS value while we work
setHotPage((AutoreleasePoolPage *)p);
if (AutoreleasePoolPage *page = coldPage()) {
if (!page->empty()) pop(page->begin()); // pop all of the pools
if (DebugMissingPools || DebugPoolAllocation) {
// pop() killed the pages already
} else {
page->kill(); // free all of the pages
}
}
// clear TLS value so TLS destruction doesn't loop
setHotPage(nil);
}
//獲取 AutoreleasePoolPage
static AutoreleasePoolPage *pageForPointer(const void *p)
{
return pageForPointer((uintptr_t)p);
}
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
uintptr_t offset = p % SIZE;
assert(offset >= sizeof(AutoreleasePoolPage));
result = (AutoreleasePoolPage *)(p - offset);
result->fastcheck();
return result;
}
//是否有空池占位符
static inline bool haveEmptyPoolPlaceholder()
{
id *tls = (id *)tls_get_direct(key);
return (tls == EMPTY_POOL_PLACEHOLDER);
}
//設(shè)置空池占位符
static inline id* setEmptyPoolPlaceholder()
{
assert(tls_get_direct(key) == nil);
tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
return EMPTY_POOL_PLACEHOLDER;
}
//獲取當(dāng)前頁
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}
//設(shè)置當(dāng)前頁
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
//獲取 coldPage
static inline AutoreleasePoolPage *coldPage()
{
AutoreleasePoolPage *result = hotPage();
if (result) {
while (result->parent) {
result = result->parent;
result->fastcheck();
}
}
return result;
}
//快速釋放
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
//添加自動釋放對象,當(dāng)頁滿的時候調(diào)用這個方法
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
assert(page == hotPage());
assert(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
//添加自動釋放對象,當(dāng)沒頁的時候使用這個方法
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
// We are pushing an object or a non-placeholder'd pool.
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
//公開方法
public:
//自動釋放
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
//入棧
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
//兼容老的 SDK 出棧方法
static void badPop(void *token)
{
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
if (DebugPoolAllocation || sdkIsAtLeast(10_12, 10_0, 10_0, 3_0)) {
// OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal.
_objc_fatal
("Invalid or prematurely-freed autorelease pool %p.", token);
}
// Old SDK. Bad pop is warned once.
static bool complained = false;
if (!complained) {
complained = true;
_objc_inform_now_and_on_crash
("Invalid or prematurely-freed autorelease pool %p. "
"Set a breakpoint on objc_autoreleasePoolInvalid to debug. "
"Proceeding anyway because the app is old "
"(SDK version " SDK_FORMAT "). Memory errors are likely.",
token, FORMAT_SDK(sdkVersion()));
}
objc_autoreleasePoolInvalid(token);
}
//出棧
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token);
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
//打印 hiwat
if (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
}
else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
static void init()
{
int r __unused = pthread_key_init_np(AutoreleasePoolPage::key,
AutoreleasePoolPage::tls_dealloc);
assert(r == 0);
}
//打印
void print()
{
_objc_inform("[%p] ................ PAGE %s %s %s", this,
full() ? "(full)" : "",
this == hotPage() ? "(hot)" : "",
this == coldPage() ? "(cold)" : "");
check(false);
for (id *p = begin(); p < next; p++) {
if (*p == POOL_BOUNDARY) {
_objc_inform("[%p] ################ POOL %p", p, p);
} else {
_objc_inform("[%p] %#16lx %s",
p, (unsigned long)*p, object_getClassName(*p));
}
}
}
//打印所有
static void printAll()
{
_objc_inform("##############");
_objc_inform("AUTORELEASE POOLS for thread %p", pthread_self());
AutoreleasePoolPage *page;
ptrdiff_t objects = 0;
for (page = coldPage(); page; page = page->child) {
objects += page->next - page->begin();
}
_objc_inform("%llu releases pending.", (unsigned long long)objects);
if (haveEmptyPoolPlaceholder()) {
_objc_inform("[%p] ................ PAGE (placeholder)",
EMPTY_POOL_PLACEHOLDER);
_objc_inform("[%p] ################ POOL (placeholder)",
EMPTY_POOL_PLACEHOLDER);
}
else {
for (page = coldPage(); page; page = page->child) {
page->print();
}
}
_objc_inform("##############");
}
//打印 hiwat
static void printHiwat()
{
// Check and propagate high water mark
// Ignore high water marks under 256 to suppress noise.
AutoreleasePoolPage *p = hotPage();
uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin());
if (mark > p->hiwat && mark > 256) {
for( ; p; p = p->parent) {
p->unprotect();
p->hiwat = mark;
p->protect();
}
_objc_inform("POOL HIGHWATER: new high water mark of %u "
"pending releases for thread %p:",
mark, pthread_self());
void *stack[128];
int count = backtrace(stack, sizeof(stack)/sizeof(stack[0]));
char **sym = backtrace_symbols(stack, count);
for (int i = 0; i < count; i++) {
_objc_inform("POOL HIGHWATER: %s", sym[i]);
}
free(sym);
}
}
#undef POOL_BOUNDARY
};
Autoreleasepool的優(yōu)化
ARC下autorelease的優(yōu)化
ARC下,runtime提供了一套對autorelease返回值的優(yōu)化策略TLS(線程局部存儲),就是為每個線程單獨分配一塊棧控件。以key-value的形式對數(shù)據(jù)進行存儲(ARC對autorelease對象的優(yōu)化標(biāo)記)。
先看優(yōu)化中涉及到的幾個函數(shù)
1、__builtin_return_address
是一個內(nèi)建函數(shù),作用是返回函數(shù)的地址,參數(shù)是層級,如果是0,則表示是當(dāng)前函數(shù)體返回地址;如果是1:則表示調(diào)用這個函數(shù)的外層函數(shù)。當(dāng)我們知道了一個函數(shù)體的返回地址的時候,就可以根據(jù)反匯編,利用某些固定的偏移量,被調(diào)方可以定位到主調(diào)放在返回值后>面的匯編指令,來確定該條函數(shù)指令調(diào)用完成后下一個調(diào)用的函數(shù)
- 這個內(nèi)建函數(shù)原型是char
- __builtin_return_address(int level),作用是得到函數(shù)的返回地址,參數(shù)表示層數(shù),如__builtin_return_address(0)表示當(dāng)前函數(shù)體返回地址,傳1是調(diào)用這個函數(shù)的外層函數(shù)的返回值地址,以此類推。
- (int)foo {
NSLog(@"%p", __builtin_return_address(0)); // 根據(jù)這個地址能找到下面ret的地址
return 1;
}
// caller
int ret = [sark foo];
2、Thread Local Storage
Thread Local Storage(TLS)線程局部存儲,目的很簡單,將一塊內(nèi)存作為某個線程專有的存儲,以key-value的形式進行讀寫,比如在非arm架構(gòu)下,使用pthread提供的方法實現(xiàn):
void* pthread_getspecific(pthread_key_t);
int pthread_setspecific(pthread_key_t , const void *);
在返回值身上調(diào)用objc_autoreleaseReturnValue方法時,runtime將這個返回值object儲存在TLS中,然后直接返回這個object(不調(diào)用autorelease);同時,在外部接收這個返回值的objc_retainAutoreleasedReturnValue里,發(fā)現(xiàn)TLS中正好存了這個對象,那么直接返回這個object(不調(diào)用retain)。
于是乎,調(diào)用方和被調(diào)方利用TLS做中轉(zhuǎn),很有默契的免去了對返回值的內(nèi)存管理。
- objc_autoreleaseReturnValue
通過__builtin_return_address(int level)檢測外部是不是ARC環(huán)境,可以替代autorelease,是的話直接返回object,不是的話調(diào)用objc_autorelease()將對象注冊到自動釋放池里面,最后通過objc_retain來獲取對象。
- objc_retainAutoreleasedReturnValue
與objc_autoreleaseReturnValue配合使用,如果在執(zhí)行完objc_autoreleaseReturnValue()這個函數(shù)的下一個調(diào)用函數(shù)是objc_retainAutoreleasedReturnValue,那么就走最優(yōu)化(在TLS中查詢關(guān)于這個對象,如果有,直接返回對象,不再對對象做retain)。
- 在調(diào)用objc_autoreleaseReturnValue的時候,會先通過__builtin_return_address這個內(nèi)建函數(shù)return address,然后根據(jù)這個地址判斷主調(diào)方在調(diào)用完objc_autoreleaseReturnValue這個函數(shù)以后是否緊接著調(diào)用了objc_retainAutoreleasedReturnValue函數(shù),如果是,那么objc_autoreleaseReturnValue()就不將返回的對象注冊到自動釋放池里面(不做autorelease),runtime會將這個返回值存儲在TLS中,做一個優(yōu)化標(biāo)記,然后直接返回這個對象給函數(shù)的調(diào)用方,在外部接收這個返回值的objc_retainAutoreleasedReturnValue()會先在TLS中查詢有沒有這個對象,如果有,那么就直接返回這個對象(不調(diào)用retain),所以通過objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue的相互配合,利用TSL做一個中轉(zhuǎn),在ARC下省去了autorelease和retain的步驟,在一定程度上達到了最優(yōu)化.
六、自動釋放池常見面試題代碼
for (int i = 0; i < 100000; ++i) {
NSString *str = @"Hello World";
str = [str stringByAppendingFormat:@"- %d",i]; //字符串拼接
str = [str uppercaseString]; //將字符串替換成大寫
}
如果循環(huán)的次數(shù)過大,會出現(xiàn)什么問題?該怎么解決?
答案:
會出現(xiàn)內(nèi)存溢出,循環(huán)內(nèi)部創(chuàng)建大量的臨時對象,沒有被釋放
每次循環(huán)都將上一次創(chuàng)建的對象release
for (int i = 0; i < 100000; ++i) {
@autoreleasepool{
NSString *str = @"Hello World";
str = [str stringByAppendingFormat:@"- %d",i];
str = [str uppercaseString];
}
}