请稍侯

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类方法。
  • NSAutoreleasePooldrain方法会调用,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();
}