1. 概述
AutoReleasePool:自动释放池
OC中的一种内存自动回收机制,它可以将加入AutoreleasePool中的变量release的时机延迟
简单来说,当创建一个对象,在正常情况下,变量会在超出其作用域时立即release。如果将其加入到自动释放池中,这个对象并不会立即释放,而会等到runloop休眠/超出autoreleasepool作用域之后进行释放
从程序启动到加载完成,主线程对应的
Runloop会处于休眠状态,等待用户交互来唤醒Runloop用户每次交互都会启动一次
Runloop,用于处理用户的所有点击、触摸等事件Runloop在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中在一次完整的
Runloop结束之前,会向自动释放池中所有对象发送release消息,然后销毁自动释放池
2. 结构
2.1 使用cpp文件探索
创建一个Mac工程,在main.m中,自动生成以下代码:
#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {NSLog(@"Hello, World!");}return 0;}
生成cpp文件
clang -rewrite-objc main.m -o main.cpp
打开cpp文件,来到main函数
int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;NSLog((NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_da0d58_mi_0);}return 0;}
autoreleasepool被注释掉了,但作用域还在- 作用域中生成对
__AtAutoreleasePool类型声明的代码
找到__AtAutoreleasePool定义
struct __AtAutoreleasePool {__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}void * atautoreleasepoolobj;};
__AtAutoreleasePool是一个结构体,包含构造函数和析构函数- 结构体声明,触发构造函数,调用
objc_autoreleasePoolPush函数 - 当结构体出作用域空间,触发析构函数,调用
objc_autoreleasePoolPop函数
2.2 使用汇编代码探索
打开项目,在main函数中autoreleasepool处设置断点,查看汇编代码:
- 调用
objc_autoreleasePoolPush函数 - 调用
objc_autoreleasePoolPop函数
进入objc_autoreleasePoolPush函数
- 源码来自于
libobjc框架
2.3 源码探索
打开objc4-818.2源码,找到objc_autoreleasePoolPush函数
void *objc_autoreleasePoolPush(void){return AutoreleasePoolPage::push();}
- 调用
AutoreleasePoolPage命名空间下的push函数
2.3.1 AutoreleasePoolPage
找到AutoreleasePoolPage的定义,首先看到这样一段注释:
/***********************************************************************Autorelease pool implementationA thread's autorelease pool is a stack of pointers.线程的自动释放池是一个指针堆栈Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.每个指针要么是一个要释放的对象,要么是POOL_BOUNDARY自动释放池边界A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.池令牌是指向该池的POOL_BOUNDARY的指针。当池被弹出,每个比哨兵热的对象都被释放The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.堆栈被分成一个双链接的页面列表。根据需要添加和删除页面Thread-local storage points to the hot page, where newly autoreleased objects are stored.线程本地存储指向热页,其中存储新自动释放的对象**********************************************************************/
通过注释我们可以了解到以下几点:
自动释放池和线程有关系
自动释放池是一个存储指针的栈结构
指针要么是一个要释放的对象,要么是
POOL_BOUNDARY自动释放池边界,俗称:哨兵对象- 哨兵对象的作用:当自动释放池将对象进行
pop操作时,需要知道边界在哪里,否则会破坏别人的内存空间。而哨兵对象,就是作为边界的标识而存在
- 哨兵对象的作用:当自动释放池将对象进行
自动释放池的栈空间被分成一个双链接结构的页面列表,可添加和删除页面
- 双向链表的特点,一个页中同时存在父节点和子节点。可向前找到父页面,也可向后找到子页面
线程本地存储指向热页,其中存储新自动释放的对象
- 栈原则,先进后出,可以理解为最后一个页面就是热页。里面的对象最后被
push,最先被pop
- 栈原则,先进后出,可以理解为最后一个页面就是热页。里面的对象最后被
AutoreleasePoolPage继承于AutoreleasePoolPageData
class AutoreleasePoolPage : private AutoreleasePoolPageData{friend struct thread_data_t;public:static size_t const SIZE =#if PROTECT_AUTORELEASEPOOLPAGE_MAX_SIZE; // must be multiple of vm page size#elsePAGE_MIN_SIZE; // size and alignment, power of 2#endifprivate:static pthread_key_t const key = AUTORELEASE_POOL_KEY;static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasingstatic size_t const COUNT = SIZE / sizeof(id);static size_t const MAX_FAULTS = 2;...}
2.3.2 AutoreleasePoolPageData
找到AutoreleasePoolPageData的定义
class AutoreleasePoolPage;struct AutoreleasePoolPageData{#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSstruct AutoreleasePoolEntry {uintptr_t ptr: 48;uintptr_t count: 16;static const uintptr_t maxCount = 65535; // 2^16 - 1};static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");#endifmagic_t const magic;__unsafe_unretained id *next;pthread_t const thread;AutoreleasePoolPage * const parent;AutoreleasePoolPage *child;uint32_t const depth;uint32_t hiwat;AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat): magic(), next(_next), thread(_thread),parent(_parent), child(nil),depth(_depth), hiwat(_hiwat){}};
结构体中,包含以下成员变量:
magic:用来校验AutoreleasePoolPage的结构是否完整next:指向最新添加的autoreleased对象的下一个位置,初始化时执行begin()thread:指向当前线程parent:指向父节点,第一个节点的parent值为nilchild:指向子节点,最后一个节点的child值为nildepth:代表深度,从0开始,往后递增1hiwat:代表high water mark最大入栈数量标记
2.3.3 打印结构
搭建测试项目,关闭ARC模式
打开main.m文件,写入以下代码:
extern void _objc_autoreleasePoolPrint(void);int main(int argc, const char * argv[]) {@autoreleasepool {NSObject *objc = [[[NSObject alloc] init] autorelease];_objc_autoreleasePoolPrint();}return 0;}
- 导入
_objc_autoreleasePoolPrint函数,用于打印自动释放池的结构 - 创建NSObject实例对象,加入自动释放池
- 调用
_objc_autoreleasePoolPrint函数,打印结构
输出以下内容:
##############AUTORELEASE POOLS for thread 0x1000ebe002 releases pending.[0x10700b000] ................ PAGE (hot) (cold)[0x10700b038] ################ POOL 0x10700b038[0x10700b040] 0x100705f60 NSObject##############
打印出当前自动释放池所属线程
有
2个需要释放的对象当前的
Page信息,占56字节。因为只有一页,即是冷页面,也是热页面哨兵对象
POOLNSObject对象
2.3.4 _objc_autoreleasePoolPrint
官方用于对自动释放池内容调试打印的函数
void_objc_autoreleasePoolPrint(void){AutoreleasePoolPage::printAll();}
进入printAll函数
static void printAll(){_objc_inform("##############");_objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_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("##############");}#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS__attribute__((noinline, cold))unsigned sumOfExtraReleases(){unsigned sumOfExtraReleases = 0;for (id *p = begin(); p < next; p++) {if (*p != POOL_BOUNDARY) {sumOfExtraReleases += ((AutoreleasePoolEntry *)p)->count;}}return sumOfExtraReleases;}#endif__attribute__((noinline, cold))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 + 256) {#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSunsigned sumOfExtraReleases = 0;#endiffor( ; p; p = p->parent) {p->unprotect();p->hiwat = mark;p->protect();#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSsumOfExtraReleases += p->sumOfExtraReleases();#endif}_objc_inform("POOL HIGHWATER: new high water mark of %u ""pending releases for thread %p:",mark, objc_thread_self());#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSif (sumOfExtraReleases > 0) {_objc_inform("POOL HIGHWATER: extra sequential autoreleases of objects: %u",sumOfExtraReleases);}#endifvoid *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};
- 按照自动释放池的结构,通过双向链表遍历
page,依次读取page中的内容并进行打印
3. 对象压栈
进入objc_autoreleasePoolPush函数
void *objc_autoreleasePoolPush(void){return AutoreleasePoolPage::push();}
进入AutoreleasePoolPage命名空间下的push函数
static inline void *push(){id *dest;if (slowpath(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;}
DebugPoolAllocation:当自动释放池按顺序弹出时停止,并允许堆调试器跟踪自动释放池- 不存在,调用
autoreleaseNewPage函数,从一个新的池页开始创建 - 否则,调用
autoreleaseFast函数,将哨兵对象压栈
3.1 autoreleaseFast
进入autoreleaseFast函数
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);}}
如果存在
page,并且没有存满,调用add函数如果存在
page,但存储已满,调用autoreleaseFullPage函数否则,不存在
page,调用autoreleaseNoPage函数
3.1.1 autoreleaseNoPage
进入autoreleaseNoPage函数
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 yetASSERT(!hotPage());bool pushExtraBoundary = false;...// 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);}
- 调用
AutoreleasePoolPage构造函数,创建新页 - 设置为热页面
pushExtraBoundary为YES,哨兵对象压栈- 对象压栈
进入AutoreleasePoolPage构造函数
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :AutoreleasePoolPageData(begin(),objc_thread_self(),newParent,newParent ? 1+newParent->depth : 0,newParent ? newParent->hiwat : 0){if (objc::PageCountWarning != -1) {checkTooMuchAutorelease();}if (parent) {parent->check();ASSERT(!parent->child);parent->unprotect();parent->child = this;parent->protect();}protect();}
- 通过父类
AutoreleasePoolPageData进行初始化 begin:获取对象压栈的起始位置objc_thread_self:通过tls获取当前线程- 链接双向链表
进入begin函数
id * begin() {return (id *) ((uint8_t *)this+sizeof(*this));}
sizeof(*this):大小取决于自身结构体中的成员变量- 返回对象可压栈的真正开始地址,在成员变量以下
进入AutoreleasePoolPageData的定义
struct AutoreleasePoolPageData{...magic_t const magic;__unsafe_unretained id *next;pthread_t const thread;AutoreleasePoolPage * const parent;AutoreleasePoolPage *child;uint32_t const depth;uint32_t hiwat;...};
magic:16字节next、thread、parent、child:各占8字节depth、hiwat:各占4字节- 共占
56字节
进入magic_t的定义
struct magic_t {static const uint32_t M0 = 0xA1A1A1A1;# define M1 "AUTORELEASE!"static const size_t M1_len = 12;uint32_t m[4];...# undef M1};
- 结构体的创建在堆区申请内存,而静态成员存储在静态区,不占结构体大小
16字节来自于uint32_t数组
进入objc_thread_self函数
static inline pthread_t objc_thread_self(){return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);}
- 通过
tls获取当前线程
3.1.2 autoreleaseFullPage
进入autoreleaseFullPage函数
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);}
- 遍历链表,找到最后一个空白的子页面
- 对其进行创建新页
- 设置为热页面
- 添加对象
形成以下数据结构:
3.1.3 add
进入add函数
id *add(id obj){ASSERT(!full());unprotect();id *ret;...ret = next; // faster than `return next-1` because of aliasing*next++ = obj;#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS// Make sure obj fits in the bits available for itASSERT(((AutoreleasePoolEntry *)ret)->ptr == (uintptr_t)obj);#endifdone:protect();return ret;}
- 使用
*next++进行内存平移 - 将对象压栈
3.2 autoreleaseNewPage
进入autoreleaseNewPage函数
id *autoreleaseNewPage(id obj){AutoreleasePoolPage *page = hotPage();if (page) return autoreleaseFullPage(obj, page);else return autoreleaseNoPage(obj);}
- 获取热页面
- 存在,调用
autoreleaseFullPage函数 - 否则,不存在
page,调用autoreleaseNoPage函数
4. autorelease
进入objc_autorelease函数
idobjc_autorelease(id obj){if (obj->isTaggedPointerOrNil()) return obj;return obj->autorelease();}
- 当前对象为
TaggedPointer或nil,直接返回 - 否则,调用对象的
autorelease函数
进入autorelease函数
inline idobjc_object::autorelease(){ASSERT(!isTaggedPointer());if (fastpath(!ISA()->hasCustomRR())) {return rootAutorelease();}return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));}
- 如果是自定义类,调用
rootAutorelease函数
进入rootAutorelease→rootAutorelease2→AutoreleasePoolPage::autorelease函数
inline idobjc_object::rootAutorelease(){if (isTaggedPointer()) return (id)this;if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;return rootAutorelease2();}idobjc_object::rootAutorelease2(){ASSERT(!isTaggedPointer());return AutoreleasePoolPage::autorelease((id)this);}static inline id autorelease(id obj){ASSERT(!obj->isTaggedPointerOrNil());id *dest __unused = autoreleaseFast(obj);#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj);#elseASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);#endifreturn obj;}
- 调用
autoreleaseFast函数,对象压栈
5. 池页容量
自动释放池采用分页的方式存储对象,因为对象在频繁压栈和出栈的过程中,产生异常,值会影响当前页面,不会影响到整个自动释放池
并且自动释放池分页管理,每页之前的地址可以不连续,它们可以使用双向链表找到父页面和子页面。如果所有对象都使用一页存储,为了保证地址的连续性,每次扩容会相对繁琐和耗时
int main(int argc, const char * argv[]) {@autoreleasepool {for (int i = 0; i < 505; i++) {NSObject *objc = [[[NSObject alloc] init] autorelease];}_objc_autoreleasePoolPrint();}return 0;}-------------------------objc[1804]: ##############objc[1804]: AUTORELEASE POOLS for thread 0x1000ebe00objc[1804]: 506 releases pending.objc[1804]: [0x10200c000] ................ PAGE (full) (cold)objc[1804]: [0x10200c038] ################ POOL 0x10200c038objc[1804]: [0x10200c040] 0x100638420 NSObjectobjc[1804]: [0x10200c048] 0x100637a40 NSObjectobjc[1804]: [0x10200c050] 0x100636970 NSObject...objc[1804]: [0x100809000] ................ PAGE (hot)objc[1804]: [0x100809038] 0x10063a0b0 NSObjectobjc[1804]: ##############
将
505个NSObject对象循环加入自动释放池,当存储504个对象时,池页已满。第505个对象创建新池页存储一页的容量:
504 * 8 = 4032,加上56字节成员变量和8字节哨兵对象,共计4096字节每一页都存在
56字节的成员变量一个自动释放池,只会压栈一个哨兵对象
在源码中查看
class AutoreleasePoolPage : private AutoreleasePoolPageData{friend struct thread_data_t;public:static size_t const SIZE =#if PROTECT_AUTORELEASEPOOLPAGE_MAX_SIZE; // must be multiple of vm page size#elsePAGE_MIN_SIZE; // size and alignment, power of 2#endif...}
来到PAGE_MIN_SIZE的定义
#define PAGE_MIN_SHIFT 12#define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)
1左移12位,相当于2 ^ 12 = 4096
6. 对象出栈
进入objc_autoreleasePoolPop函数
voidobjc_autoreleasePoolPop(void *ctxt){AutoreleasePoolPage::pop(ctxt);}
进入pop函数
static inline voidpop(void *token){AutoreleasePoolPage *page;id *stop;//判断当前对象是否为空占位符if (token == (void*)EMPTY_POOL_PLACEHOLDER) {// Popping the top-level placeholder pool.//获取热页面page = hotPage();if (!page) {// Pool was never used. Clear the placeholder.//不存在热页面,将标记设置为nilreturn setHotPage(nil);}// Pool was used. Pop its contents normally.// Pool pages remain allocated for re-use as usual.//存在热页面,通过双向链表循环向上找到最冷页面page = coldPage();//将token设置为起始位置token = page->begin();} else {//获取token所在的页page = pageForPointer(token);}//赋值给stopstop = (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//最冷页面的起始可能不是POOL_BOUNDARY://1. 弹出顶级池,保留冷页面//2. 对象在没有池的情况下被自动释放} else {// Error. For bincompat purposes this is not// fatal in executables built with old SDKs.//出现异常情况return badPop(token);}}if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {return popPageDebug(token, page, stop);}//出栈return popPage<false>(token, page, stop);}
6.1 popPage
进入popPage函数
static voidpopPage(void *token, AutoreleasePoolPage *page, id *stop){if (allowDebug && PrintPoolHiwat) printHiwat();//当前页中对象出栈,到stop位置停止page->releaseUntil(stop);// memory: delete empty childrenif (allowDebug && DebugPoolAllocation && page->empty()) {// special case: delete everything during page-per-pool debugging//特殊情况:在逐页池调试期间删除所有内容//获取父页面AutoreleasePoolPage *parent = page->parent;//销毁当前页面page->kill();//将父页面设置为热页面setHotPage(parent);} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {// special case: delete everything for pop(top)// when debugging missing autorelease pools//特殊情况:删除所有的pop//销毁当前页面page->kill();//将热页面标记设置为nilsetHotPage(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();}}}
6.2 releaseUntil
进入releaseUntil函数
void releaseUntil(id *stop){// Not recursive: we don't want to blow out the stack// if a thread accumulates a stupendous amount of garbage//向下遍历,到stop停止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();#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSAutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;// create an obj with the zeroed out top byte and release thatid obj = (id)entry->ptr;int count = (int)entry->count; // grab these before memset#else//内存平移,获取对象id obj = *--page->next;#endifmemset((void*)page->next, SCRIBBLE, sizeof(*page->next));page->protect();//当前对象不是哨兵对象if (obj != POOL_BOUNDARY) {#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS// release count+1 times since it is count of the additional// autoreleases beyond the first onefor (int i = 0; i < count + 1; i++) {objc_release(obj);}#else//将其释放objc_release(obj);#endif}}//将当前页面标记为热页面setHotPage(this);#if DEBUG// we expect any children to be completely emptyfor (AutoreleasePoolPage *page = child; page; page = page->child) {ASSERT(page->empty());}#endif}
6.3 kill
进入kill函数
void kill(){// Not recursive: we don't want to blow out the stack// if a thread accumulates a stupendous amount of garbageAutoreleasePoolPage *page = this;//循环找到最后一个子页面while (page->child) page = page->child;AutoreleasePoolPage *deathptr;do {deathptr = page;//找到父页面page = page->parent;if (page) {//将子页面设置为nilpage->unprotect();page->child = nil;page->protect();}//销毁子页面delete deathptr;//遍历销毁到this为止} while (deathptr != this);}
7. 嵌套使用
int main(int argc, const char * argv[]) {@autoreleasepool {NSObject *objc = [[[NSObject alloc] init] autorelease];@autoreleasepool {NSObject *objc = [[[NSObject alloc] init] autorelease];}_objc_autoreleasePoolPrint();}return 0;}-------------------------objc[2511]: ##############objc[2511]: AUTORELEASE POOLS for thread 0x1000ebe00objc[2511]: 4 releases pending.objc[2511]: [0x10680d000] ................ PAGE (hot) (cold)objc[2511]: [0x10680d038] ################ POOL 0x10680d038objc[2511]: [0x10680d040] 0x101370c40 NSObjectobjc[2511]: [0x10680d048] ################ POOL 0x10680d048objc[2511]: [0x10680d050] 0x101365fb0 NSObjectobjc[2511]: ##############
- 可以嵌套使用
线程的自动释放池是一个指针堆栈,当嵌套使用时,添加好各自堆栈的哨兵对象。出栈时,先释放内部,再释放外部
8. ARC模式
将项目设置为ARC模式
创建LGPerson类
#import <Foundation/Foundation.h>@interface LGPerson : NSObject+ (LGPerson *)person;@end@implementation LGPerson+ (LGPerson *)person{LGPerson *p = [[LGPerson alloc] init];return p;}@end
在main函数中,写入以下代码:
extern void _objc_autoreleasePoolPrint(void);int main(int argc, const char * argv[]) {@autoreleasepool {LGPerson *p1 = [[LGPerson alloc] init];LGPerson *p2 = [LGPerson person];_objc_autoreleasePoolPrint();}return 0;}-------------------------objc[2699]: ##############objc[2699]: AUTORELEASE POOLS for thread 0x1000ebe00objc[2699]: 2 releases pending.objc[2699]: [0x10200e000] ................ PAGE (hot) (cold)objc[2699]: [0x10200e038] ################ POOL 0x10200e038objc[2699]: [0x10200e040] 0x1013658b0 LGPersonobjc[2699]: ##############
- 只有
p2对象压栈到自动释放池
ARC模式,使用alloc、new、copy、mutableCopy前缀开头的方法进行对象创建,不会加入到自动释放池。它们的空间开辟由开发者申请,释放也由开发者进行管理
临时变量的释放:
正常情况下,超出其作用域就会立即释放
加入自动释放池,会延迟释放。当
Runloop休眠或超出autoreleasepool作用域之后释放
9. 与线程、Runloop的关系
参见官方文档:NSAutoreleasePool
9.1 与线程的关系
每个线程(包括主线程)维护自己的对象堆栈。随着新池的创建,它们被添加到堆栈的顶部。当池被释放时,它们会从堆栈中移除
autoreleased对象被放置在当前线程的顶部自动释放池中。当一个线程终止时,它会自动清空所有与其关联的自动释放池
9.2 与Runloop的关系
主程序在事件循环的每个循环开始时在主线程上创建一个自动释放池
并在结束时将其排空,从而释放在处理事件时生成的任何自动释放对象
总结
概述:
AutoReleasePool:自动释放池OC中的一种内存自动回收机制,它可以将加入AutoreleasePool中的变量release的时机延迟Runloop在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中在一次完整的
Runloop结束之前,会向自动释放池中所有对象发送release消息,然后销毁自动释放池
结构:
自动释放池的压栈和出栈,通过结构体的构造函数和析构函数触发
压栈:调用
objc_autoreleasePoolPush函数出栈:调用
objc_autoreleasePoolPop函数
特点:
自动释放池和线程有关系
自动释放池是一个存储指针的栈结构
指针要么是一个要释放的对象,要么是
POOL_BOUNDARY自动释放池边界,俗称:哨兵对象哨兵对象的作用:当自动释放池将对象进行
pop操作时,需要知道边界在哪里,否则会破坏别人的内存空间。而哨兵对象,就是作为边界的标识而存在自动释放池的栈空间被分成一个双链接结构的页面列表,可添加和删除页面
双向链表的特别,一个页中同时存在父节点和子节点。可向前找到父页面,也可向后找到子页面
线程本地存储指向热页,其中存储新自动释放的对象
栈原则,先进后出,可以理解为最后一个页面就是热页。里面的对象最后被
push,最先被pop
容量:
- 池页大小为
4096字节,每一页都包含56字节的成员变量,但一个自动释放池中,只会压栈一个哨兵对象,占8字节
原理:
自动释放池的本质是
__AtAutoreleasePool结构体,包含构造函数和析构函数结构体声明,触发构造函数,调用
objc_autoreleasePoolPush函数,本质是对象压栈的push方法当结构体出作用域空间,触发析构函数,调用
objc_autoreleasePoolPop函数,本质是对象出栈的pop方法
对象压栈
如果存在
page,并且没有存满,调用add函数使用
*next++进行内存平移将对象压栈
如果存在
page,但存储已满,调用autoreleaseFullPage函数遍历链表,找到最后一个空白的子页面
对其进行创建新页
设置为热页面
添加对象
否则,不存在
page,调用autoreleaseNoPage函数通过父类
AutoreleasePoolPageData进行初始化begin:获取对象压栈的起始位置objc_thread_self:通过tls获取当前线程链接双向链表
设置为热页面
pushExtraBoundary为YES,哨兵对象压栈对象压栈
对象出栈
调用
popPage函数,传入stop为哨兵对象的位置当前页中对象出栈,到
stop位置停止调用
kill函数,销毁当前页面
嵌套使用:
- 线程的自动释放池是一个指针堆栈,当嵌套使用时,添加好各自堆栈的哨兵对象。出栈时,先释放内部,再释放外部
ARC模式:
ARC模式,使用alloc、new、copy、mutableCopy前缀开头的方法进行对象创建,不会加入到自动释放池。它们的空间开辟由开发者申请,释放也由开发者进行管理
与线程的关系:
每个线程(包括主线程)维护自己的对象堆栈。随着新池的创建,它们被添加到堆栈的顶部。当池被释放时,它们会从堆栈中移除
autoreleased对象被放置在当前线程的顶部自动释放池中。当一个线程终止时,它会自动清空所有与其关联的自动释放池
与Runloop的关系:
主程序在事件循环的每个循环开始时在主线程上创建一个自动释放池
并在结束时将其排空,从而释放在处理事件时生成的任何自动释放对象
