Autorelease 及 Autoreleasepool 原理
04 December 2020
Autorelease 及 Autoreleasepool 原理
自动释放池在 MRC 模式与 ARC 模式下还是有些差异的。
MRC模式
autorelease
会像C语言的自动变量那样来对待对象实例。当超出变量作用域时,对象实例的release实例方法被调用。但同C语言自动变量不同的是,开发者可以设定变量的作用域,而其中的NSAutoreleasePool对象的生存周期就相当于C语言变量的作用域。- 要使用
autorealse
就需要生成并持有NSAutoreleasePool
对象,而后把所有的autorealse
实例添加到NSAutoreleasePool
里,离开作用时,会循环的向NSAutoreleasePool
里所有的autorealse
实例发送release
消息,并废弃NSAutoreleasePool
对象。 - 程序的主循环的
NSRunLoop
每次循环过程中会对NSAutoreleasePool
对象进行生成、和废弃,iOS程序在main
方法里最外层套了一层autoreleasepool
,所以只要这最外层的autoreleasepool
对象不废弃,那添加在其中的autorelease
对象就不会被释放。 autorealse
实例方法本质上就是调用了NSAutoreleasePool
对象的addObject
类方法。NSAutoreleasePool
的drain
方法会调用,autorelease
方法已被该类重载,因此运行就会出错。- 通过指定编译参数
-fobjc-arc
后,编译器会自动添加引用计数
相关的代码;
ARC模式 在ARC下是使用@autoreleasepool{}
来使用一个AutoreleasePool的,编译时编译器会将其改写这样:
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
这两个函数都是对AutoreleasePoolPage的简单封装,AutoreleasePoolPage是一个C++实现的类。其内存结构图如下:
- AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针);
- AutoreleasePool是按线程一一对应的(AutoreleasePoolPage结构中的thread指针指向当前线程);
- 每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址;
- next 指向了下一个为空的内存地址,如果 next指向的地址加入一个 object,它就会如下图所示移动到下一个为空的内存地址中,上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置;
- 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入;
- 向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置;
POOL_SENTINEL
POOL_SENTINEL
是个哨兵对象,它只是nil的别名;- 在每个自动释放池初始化调用
objc_autoreleasePoolPush
的时候,都会把一个POOL_SENTINEL
push
到自动释放池的栈顶,并且返回这个POOL_SENTINEL
哨兵对象; - 当方法
objc_autoreleasePoolPop
调用时,就会向自动释放池中的对象发送release消息,直到第一个POOL_SENTINEL
; - objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)作为入参;
- 当
pop
时,会根据传入的哨兵对象地址找到哨兵对象所处的page,在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release
消息,并向回移动next指针到正确位置,另外在清理过程当前,可以向前跨越若干个page,直到哨兵所在的page;
AutoreleasePoolPage 源码节选
class AutoreleasePoolPage
{
// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is
...
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent; //这里表明它是个双向链表
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
...
}
AutoreleasePoolPage(AutoreleasePoolPage *newParent)
: magic(), next(begin()), thread(pthread_self()),
parent(newParent), child(nil),
...
}
~AutoreleasePoolPage()
{
check();
...
// 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
...
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
...
// 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;
...
// 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) {
...
}
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();
...
}
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
...
}
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
...
}
static inline AutoreleasePoolPage *coldPage()
{
AutoreleasePoolPage *result = hotPage();
if (result) {
while (result->parent) {
...
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
...
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
...
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
...
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
...
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
....
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
....
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
....
static void init()
{
int r __unused = pthread_key_init_np(AutoreleasePoolPage::key,
AutoreleasePoolPage::tls_dealloc);
assert(r == 0);
}
....
_objc_inform("AUTORELEASE POOLS for thread %p", pthread_self());
AutoreleasePoolPage *page;
ptrdiff_t objects = 0;
for (page = coldPage(); page; page = page->child) {
....
// 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) {
....
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
....
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
....
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
....
_objc_autoreleasePoolPrint(void)
{
AutoreleasePoolPage::printAll();
}
....
void arr_init(void)
{
AutoreleasePoolPage::init();
SideTableInit();
}