iOS自动释放池AutoreleasePool

引言

我们项目的main函数中有一个自动释放池@autoreleasepool,我们的代码都是运行在这个自动释放池之中

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"自动释放池");
    }
    return 0;
}
复制代码

我们通过查看其底层代码实现逻辑查看其实现细节

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
复制代码

打开main.cpp文件查看

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;
};


int main(int argc, char * argv[]) {
 /* @autoreleasepool */ { 
 // 触发构造函数
 __AtAutoreleasePool __autoreleasepool; 
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_rd9sd5vj7_l6z9w7rpcd83g80000gn_T_main_22bdd0_mi_0);
        
//出了作用域__autoreleasepool要释放触发析构函数
 }
    return 0;
}
复制代码

从这里了解到自动释放池的底层实现为

{
    atautoreleasepoolobj = objc_autoreleasePoolPush();

    //这里写自己的代码

    objc_autoreleasePoolPop(atautoreleasepoolobj);
}
复制代码

那么objc_autoreleasePoolPushobjc_autoreleasePoolPop方法就是我们研究的重点

autoreleasepool介绍

An autorelease pool stores objects that are sent a release message when the pool itself is drained.

当自动释放池倾倒的时候会给里面存储的对象发送release消息

In a reference-counted environment (as opposed to one which uses garbage collection), an NSAutoreleasePool object contains objects that have received an autorelease message and when drained it sends a release message to each of those objects. Thus, sending autorelease instead of release to an object extends the lifetime of that object at least until the pool itself is drained (it may be longer if the object is subsequently retained). An object can be put into the same pool several times, in which case it receives a release message for each time it was put into the pool.

在引用计数环境中,被放入自动释放池的是那些收到autorelease消息的对象,当自动释放池倾倒的时候这些对象会收到release消息。给对象发autorelease消息而不是release消息的作用是将对象的声明周期延长到自动释放池倾倒的时候,如果被外部引用对象的生命周期会更长。

一个对象可以被多次添加到同一个自动释放池中,每次添加都会收到一个release消息(这句话不太理解,我们在后面源码中寻找答案)

If your application creates a lot of temporary autoreleased objects within the event loop, however, it may be beneficial to create “local” autorelease pools to help to minimize the peak memory footprint.

扫描二维码关注公众号,回复: 13166308 查看本文章

如果你写了很多临时变量可以使用自动释放池缓解内存压力

例如我们在for循环中创建了很多临时变量,那么这些临时变量直到for循环结束才会被释放,如果for循环的次数很多那么就会造成内存使用激增,可以在for循环中使用自动释放池避免内存瞬时使用量过大

源码

源码版本是objc4-781.2

数据结构

因为AutoreleasePoolPage继承自AutoreleasePoolPageData,我们先来看AutoreleasePoolPageData的结构

class AutoreleasePoolPage : private AutoreleasePoolPageData{...}
复制代码
AutoreleasePoolPageData
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    magic_t const magic; // 4*4=16字节
    __unsafe_unretained id *next; //8 指向下一个存储位置
    pthread_t const thread; //8 当前线程
    AutoreleasePoolPage * const parent; //8 指向上一页
    AutoreleasePoolPage *child; //8 指向下一页
    uint32_t const depth; //4 当前页在双向链表中的深度
    uint32_t hiwat; //4 

    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)
    {
    }
};

复制代码
  • 一个AutoreleasePoolPageData结构体占用56个字节,每一个page的前56个字节是存储当前page的信息,后面才开始存储存储需要被自动释放的信息
AutoreleasePoolPage
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    #define POOL_BOUNDARY nil
    
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),
        objc_thread_self(),
        newParent,
        newParent ? 1+newParent->depth : 0,
        newParent ? newParent->hiwat : 0)
    { 
        if (parent) {
            parent->check();
            ASSERT(!parent->child);
            parent->unprotect();
            parent->child = this;
            parent->protect();
        }
        protect();
    }

    ~AutoreleasePoolPage() 
    {
        check();
        unprotect();
        ASSERT(empty());

        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        ASSERT(!child);
    }
    
    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

    
    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

    bool empty() {
        return next == begin();
    }

    bool full() { 
        return next == end();
    }
    
    id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing 这么做效率较高
        *next++ = obj;
        protect();
        return ret;
    }
    
    ...
    releaseAll、kill等方法
    ...
}
复制代码

自动释放池的数据结构是分页实现的,以双向链表管理这些页,每一页的起始位置存储当前页的信息,也就是AutoreleasePoolPageData中的成员

相关函数

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

static inline void *push() 
    {
        id *dest;
        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts on a new pool page.
            // 每一个自动释放池都是从autoreleaseNewPage方法开始的
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
 
复制代码
autoreleaseNewPage
static __attribute__((noinline))
    id *autoreleaseNewPage(id obj)
    {
        // 这个方法获取当前链表最后一页,也就是标记为`hot`的页
        AutoreleasePoolPage *page = hotPage();
        
        // 如果存在则调用autoreleaseFullPage方法处理是否存满的逻辑
        if (page) return autoreleaseFullPage(obj, page);
        
        // 第一次进来不存在page则调用autoreleaseNoPage处理开辟新页面的逻辑
        else return autoreleaseNoPage(obj);
    }
复制代码
hotPage
static inline AutoreleasePoolPage *hotPage() 
    {
        // 从当前线程的缓存tls中获取hotpage
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
            
        // 如果不存在返回nil,说明是第一次进来    
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        
        // 如果存在则执行fastcheck进行一些校验工作
        if (result) result->fastcheck();
        return result;
    }
复制代码

自动释放池创建成功之后将链表中最后一个节点页标记为hotpage,使用线程的tls进行缓存

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);

        // 顺着链表查找,如果最后一个节点页不满,那么page就指向最后一个节点
        // 如果最后一个节点也满了,那么新开辟一个字节点,page指向新节点
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        // 将新节点标记为hot
        setHotPage(page);
        
        // 将对象obj添加到page中,并返回page指针
        return page->add(obj);
    }
复制代码
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
        // 执行这个函数有两种情况,第一种是没有创建过pool,第二种是创建了但是没有存储任何内容
        ASSERT(!hotPage());

        bool pushExtraBoundary = false;
        
        // tls中没有缓存过
        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;
        }
        
        // tls中已经缓存过了,这里应该是设置哨兵对象,所以obj应该等于POOL_BOUNDARY
        // obj != POOL_BOUNDARY 就报错了
        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;
        }
        
        // pool已经存在,这里要设置哨兵对象
        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);
        
        // 将page指针存储到tls中
        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);
    }
复制代码
autoreleaseFast
    static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            // page存在并且没有存满
            return page->add(obj);
        } else if (page) {
            // 处理page满了的逻辑
            return autoreleaseFullPage(obj, page);
        } else {
            // 处理page不存在的逻辑
            return autoreleaseNoPage(obj);
        }
    }
复制代码

以上我们梳理了怎么创建自动释放池、怎么开辟新的page、怎么存储obj的逻辑,下面我们看一下释放的逻辑

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

static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        
        // 找到token所在的page,token指向page的begin位置
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            token = page->begin();
        } else {
            page = pageForPointer(token);
        }

        stop = (id *)token;
        // 处理异常情况
        // 注意 *stop != POOL_BOUNDARY 是异常情况
        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 (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }

        // 这里*stop == POOL_BOUNDARY 
        // 最终要执行popPage函数
        return popPage<false>(token, page, stop);
    }
复制代码

这里

pageForPointer
static AutoreleasePoolPage *pageForPointer(const void *p) 
    {
        return pageForPointer((uintptr_t)p);
    }

    static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
    {
        AutoreleasePoolPage *result;
        // p为token指针所在地址
        // SIZE为page的大小
        // p对SIZE取余得到offset为p在当前page中的偏移
        uintptr_t offset = p % SIZE;
        
        // 通过前面数据结构我们知道AutoreleasePoolPage的前56字节存储当前page的信息
        // 所以offset >= 56 (仅针对当前版本为56)
        ASSERT(offset >= sizeof(AutoreleasePoolPage));
        
        // p-offset可以得到p所在page的首地址
        result = (AutoreleasePoolPage *)(p - offset);
        
        result->fastcheck();

        return result;
    }
复制代码
popPage
template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // 这里要删除相关page
        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && 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) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
复制代码
releaseUntil
void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        
        // 注意这里的stop为哨兵对象
        // 哨兵对象作为释放的结束标志
        while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            // 当前page释放完了,该释放上一页了
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
            // next指针表示下一个要插入的位置,是没有值的
            // 通过--操作找到上一个指针位置,即obj的位置
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();

            if (obj != POOL_BOUNDARY) {
                // 将对象release
                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
    }
复制代码

这个方法是自动释放池倾倒的操作,传入的stop为哨兵对象,哨兵对象为自动释放池的开始位置,这里将其作为自动释放池倾倒的结束为止

void kill() 
    {
        // 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;
            page = page->parent;
            if (page) {
                page->unprotect();
                // 将page的child指针置为nil
                page->child = nil;
                page->protect();
            }
            delete deathptr;
        } while (deathptr != this);
    }
复制代码

到这里我们了解了自动释放池是分页存储的,不同页通过childparent指针组织称双向链表,这样的好处也很好理解,通过分页存储可以利用零散的存储空间,否则的话就需要一块很大的内存空间,还会涉及到扩容的问题。

每一页中的前56字节(仅针对当前版本)存储page自己的信息,后面才开始存储需要被自动释放的对象指针

链表的第一页的第一个对象存储哨兵对象POOL_BOUNDARY,作为开始的标记和释放结束的标记

链表的最后一页作为hotPage

到这里我们对自动释放池有了一个基本的认识。

参考文章

# iOS-自动释放池AutoreleasePool

猜你喜欢

转载自juejin.im/post/7017695839250284581