Objective-c内存管理之自动释放池

630 阅读6分钟

在Objective-C开发过程中使用自动释放池时,代码一般是长这样的:

  @autoreleasepool{
    //代码  
  } 

然而编译后,代码就变成了这样:

 { 
  __AtAutoreleasePool __autoreleasepool;  
  //代码
 }

@autoreleasepool{}编译之后就变成一个局部作用域加上结构体变量的定义 __AtAutoreleasePool的定义如下:

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

__AtAutoreleasePool是一个C++ 结构体, 在其和构造方法和析构方法中分别调用了 objc_autoreleasePoolPush()objc_autoreleasePoolPop(atautoreleasepoolobj)

进入自动释放池: objc_autoreleasePoolPush()

来看看 objc_autoreleasePoolPush() 相关的代码:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

class AutoreleasePoolPage {
 #   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);
   
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
  
  ...
}

自动释放池的进入就是调用了一个C++类 AutoreleasePoolPage 的push方法。AutoreleasePoolPage负责管理自动释放池的逻辑。

通过阅读AutoreleasePoolPage成员信息 可以大致了解到AutoreleasePoolPage是以双向链表的形势来做管理的,next 数组成员保存外界引用数据的数组。在接下来阅读源码的过程中心中已经有了一个大致逻辑。

来看看AutoreleasePoolPage::push相关源码:

static inline void *push() 
{
  id *dest;
  if (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;
}
    
 static inline id *autoreleaseFast(id obj)
    {   
        //获取热的 AutoreleasePoolPage
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
             //热页没有满 ,则添加的热页中
            return page->add(obj);
        } else if (page) {
             //处理热页满的情况
            return autoreleaseFullPage(obj, page);
        } else {
            //处理没有热页的情况
            return autoreleaseNoPage(obj);
        }
    }
    
    //处理页满
     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);
        
        //沿着child方向一直找到第一个没有满的页
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
        //设置其为hot page
        setHotPage(page);
        //将对象放到page中
        return page->add(obj);
    }
   
   //处理没有页的情况
   static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
    
        assert(!hotPage());

        bool pushExtraBoundary = false;
        //存在空释放池占位符
        if (haveEmptyPoolPlaceholder()) {
            //标记push一个标记作为占位符
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
                
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         pthread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
           //对于没有占位符,也没有自动释放池的情况,先为线程设置一个空释放池占位标记
            return setEmptyPoolPlaceholder();
        }

        //到了这一步说明线程中还没有自动释放池
        //所以先创建自动释放池,并且标记为热页
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        //放入POOL_BOUNDARY 表示该自动释放池页的
        //前面的页是一个占位的页
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        return page->add(obj);
    }
    
    id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        //直接设置把元素设置到next数组中
        *next++ = obj;
        protect();
        return ret;
    }

对于自动释放池的push过程,总结以下两点:

1.自动释放池是分页管理的,代表页的数据结构是C++类 AutoreleasePoolPage,并且每页之间支持双向查找

2.自动释放池分页有热页的概念,热页即当前页,热页满了才会创建新的页

自动释放池热页的管理

static inline void setHotPage(AutoreleasePoolPage *page) 
    {
        if (page) page->fastcheck();
        //热页和线程关联
        tls_set_direct(key, (void *)page);
    }
    
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;
}

可以看到,设置一个页为热页直接使其和tls做了关联,而获取热页也只是从tls中获取。

出自动释放池: objc_autoreleasePoolPop

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            
            //token 为 EMPTY_POOL_PLACEHOLDER 时说明要清理所有的页 
            if (hotPage()) {
               //存在hot page,则从page->parent方向遍历递归pop
                pop(coldPage()->begin());
            } else {
                //热页不存在清空热页
                setHotPage(nil);
            }
            return;
        }

        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.
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();
       
        //page向parent方向遍历,然后对page中的对象调用objc_realease()
        page->releaseUntil(stop);

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
           //如果页面超过半满,则保留一个空子项
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }



 void releaseUntil(id *stop) 
    {

        
        while (this->next != stop) {

            AutoreleasePoolPage *page = hotPage();

            //当前页为空,将parent页设置为热页
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }
            
            page->unprotect();
            //遍历next数组
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();
           
            //释放对象
            if (obj != 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
    }

可以看到,在出释放池时,对要操作的页遍历next数组,对next数组成员中的对象调用objc_realase方法。

自动释放池结构图

引申问题

对象是如何进入自动释放池的?

通过上面的源码分析,我发现objc_autoreleasePoolPush() 方法调用后进入到自动释放池的都是POOL_BOUNDARY分割符。那么对象是如何进入自动释放池的?

对于不以alloc/create/new/copy等前缀的方法创建的对象,系统会对创建好的对象发送autorelease消息,将创建的对象放入自动释放池。 对象调用autorelase方法后最终会来到 AutoreleasePoolPage::autorelease(obj)方法。

static inline id autorelease(id obj)
{
   assert(obj);
   assert(!obj->isTaggedPointer());
   //将对象放入自动释放池
   id *dest __unused = autoreleaseFast(obj);
   assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
   return obj;
}

循环次数很大的循环操作为什么使用自动释放池就能很好的降低内存峰值?

这也是因为循环中使用了自定义的释放池,系统会把循环创建的autorelease 对象都加入循环内的自动释放池中。而每一次循环结束都会触发循环内自动释放池的pop操作,这样使得加入到循环内自动释放池的对象很快地得到了释放。从而减少了内存峰值。

如果循环内不加自动释放池,那么创建的autorelease 对象都会加入到线程的自动释放池当中,这样只能等待runloop来处理,这样就延长循环内创建的autorelease 对象的生命周期,循环一定次数后造成内存占用峰值过高。