/*********************************************************************** Autorelease pool implementation A 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. 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. 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. **********************************************************************/
public: staticsize_tconst SIZE = #if PROTECT_AUTORELEASEPOOL PAGE_MAX_SIZE; // must be multiple of vm page size #else PAGE_MIN_SIZE; // size and alignment, power of 2 //4KB #endif private: staticpthread_key_tconst key = AUTORELEASE_POOL_KEY; //AUTORELEASE_POOL_KEY:43 staticuint8_tconst SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing staticsize_tconst COUNT = SIZE / sizeof(id);
// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is // pushed and it has never contained any objects. This saves memory // when the top level (i.e. libdispatch) pushes and pops pools but // never uses them. # define EMPTY_POOL_PLACEHOLDER ((id*)1) //占位池,起到优化作用。因为很可能创建了自动释放池但实际没有使用到自动释放对象,使用占位池就可以避免这种损耗。
# define POOL_BOUNDARY nil //哨兵对象,清空本次自动释放池里的对象的截止位置。 ... ... ... id * begin(){ return (id *) ((uint8_t *)this+sizeof(*this)); }
id * end(){ return (id *) ((uint8_t *)this+SIZE); }
staticinlinevoid *push() { id *dest; if (slowpath(DebugPoolAllocation)) { // Each autorelease pool starts on a new pool page. dest = autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); //注意这里的参数是POOL_BOUNDARY } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; //返回的是page上的一个地址。 }
staticpthread_key_tconst key = AUTORELEASE_POOL_KEY; define AUTORELEASE_POOL_KEY((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3) #define __PTK_FRAMEWORK_OBJC_KEY3 43 // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is // pushed and it has never contained any objects. This saves memory // when the top level (i.e. libdispatch) pushes and pops pools but // never uses them. # define EMPTY_POOL_PLACEHOLDER ((id*)1)
staticinlinevoidsetHotPage(AutoreleasePoolPage *page) { if (page) page->fastcheck(); tls_set_direct(key, (void *)page); }
逻辑很简单就是把page的地址存储到线程的TLS。
与hotPage对应的是codePage
codePage
实现:
1 2 3 4 5 6 7 8 9 10 11
staticinline AutoreleasePoolPage *coldPage() { AutoreleasePoolPage *result = hotPage(); if (result) { while (result->parent) { result = result->parent; result->fastcheck(); } } return result; }
其实就是找到双链表的第一个page。即双链表的第一个page也叫coldPage。
接着未完成的分析,autoreleaseFullPage
autoreleaseFullPage
自动释放池页满了的处理。
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
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());
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; } elseif (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", objc_thread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } elseif (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); }
staticinlineboolhaveEmptyPoolPlaceholder() { id *tls = (id *)tls_get_direct(key); return (tls == EMPTY_POOL_PLACEHOLDER); }
staticinlinevoid pop(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. returnsetHotPage(nil); } // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. page = coldPage(); token = page->begin(); //coldPage是双链表的第一页,这里将token设置为begin位置。 } else { 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. returnbadPop(token); } }
voidreleaseUntil(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) { //一直释放到stop的位置。 // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); //先获取hotPage.
// fixme I think this `while` can be `if`, but I can't prove it while (page->empty()) { //如果是空的,说明这一页已经清空,则继续往上找,最终page会等于this。随着释放this->next最终会等于stop. page = page->parent; setHotPage(page); }
if (obj != POOL_BOUNDARY) { //这里很妙,如果obj不是nil也会释放掉。因为有可能page里就没有POOL_BOUNDARY对象。不过这种情况一般不会出现,因为系统在新开线程的时候会帮你执行push操作所以基本上都会存在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 }
Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools are created, they get added tothe top ofthe stack. When pools are deallocated, they are removed fromthe stack. Autoreleased objects are placed intothe top autorelease pool forthe current thread. When a thread terminates, it automatically drains all ofthe autorelease pools associated with itself.
staticvoidinit()//静态方法 { int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, AutoreleasePoolPage::tls_dealloc); ASSERT(r == 0); }
... #if !VARIANT_DYLD // XXX: key should be pthread_key_t int pthread_key_init_np(int key, void (*destructor)(void *)) { int res = EINVAL; // Returns EINVAL if key is out of range. if (key >= __pthread_tsd_first && key < __pthread_tsd_start) { _PTHREAD_LOCK(__pthread_tsd_lock); _pthread_key_set_destructor(key, destructor); if (key > __pthread_tsd_max) { __pthread_tsd_max = key; } _PTHREAD_UNLOCK(__pthread_tsd_lock); res = 0; } return res; } #endif// !VARIANT_DYLD
staticvoidtls_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()) objc_autoreleasePoolPop(page->begin()); // pop all of the pools if (slowpath(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); }