iOS - autoreleasepool 源码
AutoreleasePool:自动释放池是 Objective-C 开发中的一种自动内存回收管理的机制,为了替代开发人员手动管理内存,实质上是使用编译器在适当的位置插入release、autorelease等内存释放操作。当对象调用 autorelease 方法后会被放到自动释放池中延迟释放时机,当缓存池需要清除dealloc时,会向这些 Autoreleased 对象做 release 释放操作。
结论:
push时
(1). 当前page存在且没有满时,直接将对象添加到当前page中,即next指向的位置;
(2). 当前page存在并且已满时,创建一个新的page,并将对象添加到新创建的page 中,然后将这两个链表节点进行链接。
(3). 当前page不存在时,创建第一个page ,并将对象添加到新创建的page中。
pop时
这里的stop同样是POOL_BOUNDARY的地址
(1). 外部循环挨个遍历autoreleased 对象,直到遍历到哨兵对象POOL_BOUNDARY。
(2). 如果当前page没有 POOL_BOUNDARY,并且为空,则将hotPage设置为当前page的父节点。
(3). 给当前autoreleased对象发送release消息。
(4). 最后再次配置hotPage。
autoreleasepool与子线程
在NSThread退出了之后,与NSThread对应的AutoReleasePool也会被自动清空,所以当一个线程结束的时候,就会回收♻️AutoReleasePool中自动释放的对象。
每一个线程都会维护自己的AutoReleasePool,而每一个AutoReleasePool都会对应唯一一个线程,但是线程可以对应多个AutoReleasePool。
autoreleasepool与NSRunloop
子线程在使用autorelease对象的时候,会懒加载出来一个AutoreleasePoolPage,然后将对象插入进去。
那么问题又来了,autorelease对象在什么时候释放的呢?也就说AutoreleasePoolPage在什么时候调用了pop方法?
其实在上面创建一个NSThread的时候,在调用[NSthread exit]的时候,会释放当前资源,也就是把当前线程关联的autoReleasePool释放掉,而在这里当RunLoop执行完成退出的时候,也会执行pop方法,这就说明了为什么在子线程当中,我们没有显示的调用pop,它也能释放当前AutoreleasePool的资源的原因。
autoreleasepool与主线程
0x1代表的是kCFRunLoopEntry,也就是说第一个 Observer监视的事件是Entry(即将进入Loop时),其回调内会调用_objc_autoreleasePoolPush()创建一个自动释放池。其order优先级是-2147483647,优先级最高,保证创建自动释放池发生在其他所有回调之前。
0xa0对应的是kCFRunLoopBeforeWaiting和kCFRunLoopExit,也就是说第二个Observer监视了两个事件:kCFRunLoopBeforeWaiting准备进入休眠,kCFRunLoopExit即将退出RunLoop。在kCFRunLoopBeforeWaiting事件时调用 _objc_autoreleasePoolPop()和_objc_autoreleasePoolPush() 释放旧的自动释放池并创建新的自动释放池;在kCFRunLoopExit事件时调用_objc_autoreleasePoolPop() 来释放自动释放池,同时这个Observer的order优先级是 2147483647,优先级最低,保证其释放自动释放池的操作发生在其他所有回调之后。
所以在没有手动增加AutoreleasePool的情况下,Autorelease对象都是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池push和pop操作。
总结:
对于不同线程,应当创建自己的AutoReleasePool。如果应用长期存在,应该定期drain和创建新的AutoReleasePool,AutoReleasePool与RunLoop 与线程是一一对应的关系,AutoReleasePool在RunLoop在开始迭代时做push操作,在RunLoop休眠或者迭代结束时做pop操作。
源码
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_44_1ht3l6g55dv59_5s62wsv_bm0000gn_T_ViewController_3cfd72_mi_0);
}
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;
};
每个AutoreleasePoolPage大小是4kb集4096字节
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
struct 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!");
#endif
magic_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)
{
}
};
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
的定义
//翻译:当自动释放池无序弹出时停止,并允许堆调试器跟踪自动释放池
OPTION( DebugPoolAllocation, OBJC_DEBUG_POOL_ALLOCATION, "halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools")
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
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);
}
}
hotPage
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;
}
autoreleaseFullPage
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);
}
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",
objc_thread_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);
}