OC_@autoreleasePool

143 阅读5分钟

自动释放池

oc中内存自动回收的一种机制,可以延长释放池中变量释放的时机,可以防止项目在运行过程中内存峰值过高。一般情况下创建的变量当超出其的作用域时,变量就会被释放。如果将变量加入到释放池,它的释放的时机就会被延迟。

哨兵对象

为nil,表示 autoreleasePool 的内存边界,多个 @autoreleasePool 进行嵌套时,用于区分存储的数据是否属于当前内存释放池。一个自动释放池只存在一个 哨兵对象

autoreleasePool 运行流程

当第一次进入到 @autoreleasePool 中,objc_autoreleasePoolPush() 就会被调用,新建一个 AutoreleaseNewPage(), 然后会向其中添加一个 nil对象 作为哨兵对象,并返回该对象的地址。
当对象调用 autorelease 方法时,就会被添加到 AutoreleasePoolPage()中,若当前 page 不足以存放对象时,就会新建一个新的 AutoreleasePoolPage()
当要离开 @autoreleasePool 作用域时,objc_autoreleasePoolPop() 就会被调用,然后对page中的对象进行释放,一直到释放到哨兵对象为止

autoreleasepool() 方法结构

- objc_autoreleasePoolPush()
    - push()
        - autoreleaseNewPage()
            - autoreleaseFullPage()
            - autoreleaseNoPage()
        - autoreleaseFast()
            - page->add(obj)
            - autoreleaseFullPage()
            - autoreleaseNoPage()
- objc_autoreleasePoolPop()
    - pop(ctxt)
        - popPage()
            - releaseUntil()

详细流程

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

image.png

可以看到,在使用了 @autoreleasepool{},在底层其实是调用了 objc_autoreleasePoolPush()objc_autoreleasePoolPop() 方法

  • objc_autoreleasePoolPush()
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
  • AutoreleasePoolPage()
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    ...
    public:
    //#define PAGE_MIN_SHIFT          12
    //#define PAGE_MIN_SIZE           (1 << PAGE_MIN_SHIFT)
    static size_t const SIZE = PAGE_MIN_SIZE;
    ...
}

由上面代码可以知道 AutoreleasePoolPage 是继承于 AutoreleasePoolPageData。其一个page 大小是 1<<12 = 4kb, 池中每个节点都是由 AutoreleasePoolPage() 组成,

struct AutoreleasePoolPageData
{
    ...
    magic_t const magic;                 //校验 AutoreleasePoolPage 的结构是否完整 占位
    16字节
    __unsafe_unretained id *next;        //指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() 8字节
    pthread_t const thread;              //指向当前线程  8字节
    AutoreleasePoolPage * const parent;  //指向父节点,第一个节点的 parent 为 nil  8字节
    AutoreleasePoolPage *child;          //指向子节点,最后一个节点的 child 为 nil  8字节
    uint32_t const depth;                //深度,从 0 开始,向后递增 1  4字节
    uint32_t hiwat;                      //最大入栈数量标记  4字节
   ...
}
  • push():若是首次进入调用 autoreleaseNewPag() 去添加哨兵对象,不是就调用 autoreleaseFast() 去添加哨兵对象
static inline void *push()     
{
    id *dest;
    if (slowpath(DebugPoolAllocation)) {
        // Each autorelease pool starts on a new pool page.  创建一个新的page
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}
  • autoreleaseNewPage():通过 hotPage() 寻找当前的 AutoreleasePoolPage 的节点,若 page 存在就插入 objc,若 page 不存在就新建一个 page
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
    AutoreleasePoolPage *page = hotPage();
    if (page) return autoreleaseFullPage(obj, page);
    else return autoreleaseNoPage(obj);
}
  • hotPage():获取当前操作的 page
static inline AutoreleasePoolPage *hotPage() 
{
    //tls_get_direct:寻找线程的快速缓存
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)tls_get_direct(key);
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
    //page 存在,返回page
    if (result) result->fastcheck(); 
    return result;
}
  • autoreleaseNoPage():创建第一页的数据,并将哨兵对象添加到其中
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.
    //初始化第一页 page,AutoreleasePoolPage
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    //设置第一页page 为 hot page
    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.
    //向page里面插入 obj
    return page->add(obj);
}
  • 初始化 AutoreleasePoolPage()
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
    AutoreleasePoolPageData(begin(),                            //计算数据插入的位置
                            objc_thread_self(),                 //当前线程
                            newParent,                          //当前父节点
                            newParent ? 1+newParent->depth : 0, //page中对象的深度
                            newParent ? newParent->hiwat : 0)
    {
        if (objc::PageCountWarning != -1) {
            checkTooMuchAutorelease();
        }
        if (parent) {
            parent->check();
            ASSERT(!parent->child);
            parent->unprotect();
            parent->child = this;
            parent->protect();
        }
        protect();
}

在向 AutoreleasePoolPage 添加数据的时候,他的起始位置是 当前的page的起始位置 加上 page大小 之后的位置开始。
AutoreleasePoolPageData 中的数据占用空间为 56 个字节,在向其中插入数据的位置将会从 第56个字节 开始

  • autoreleaseFast():当page已经存在时调用
static inline id *autoreleaseFast(id obj)
{
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {                //page 存在,并且 page 中空间未被站满
        return page->add(obj);
    } else if (page) {                          //page 存在,并且空间已经使用完了
        return autoreleaseFullPage(obj, page);
    } else {                                    //page 不存在,就新建
        return autoreleaseNoPage(obj);
    }
}
  • autoreleaseFullPage():去判断当前的 page 空间是否满了,如果满了,通过遍历去寻找 page 的子节点,如果没有找到子节点,就去新建一个 page,后面进行一个 插入数据的操作
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
    ...
    do {
        if (page->child) page = page->child;       //遍历寻找子节点
        else page = new AutoreleasePoolPage(page); //未找到子节点,就新建一个 page
    } while (page->full());
    setHotPage(page);
    return page->add(obj);
}

__autoreleasing

@autoreleasepool {
    __autoreleasing NSObject *obj = [NSObject alloc];
}

标记一个要被添加到自动释放池中的对象 由 alloc, new, copy, mutableCopy 创建的对象,内存由 ARC 进行分配释放,不会被加入到自动释放池中,需要添加关键词 __autoreleasing 才会被加入到自动释放池中

通过汇编可以看到底层调用了 objc_autorelease image.png 继续往下走,最终会调用 autoreleaseFast(id obj)

结尾补充

ARC下,autorelease 对象什么时候释放?
分两种情况,如果是手动创建的 autoreleasePool,则在释放池的作用域结束时候进行释放。
如果没有进行手动创建 autoreleasePool,若是在主线程,就会在 runloop 即将休眠的时候,进行释放。若是在子线程,就会在子线程被销毁的时候,进行释放

func1() : func2()
func2() 给 func1()的类的成员变量进行赋值

autoreleasePool 和 runloop 的关系
当程序启动的时候,主线程的 runloop 会自动启动,并且会注册 runloop 的两个监听:

  • 一个当runloop 将要被唤醒的时候,会自动创建 自动释放池,这个事件优先级最高,其目的是能让runloop中的事件能够加入到 autoreleasePool 中。
  • 一个是当 runloop 将要进入到休眠的时候,会释放自动释放池,这个事件优先级最低,目的是让 runloop 里面的事件先于释放 autoreleasePool 这个事件完成。