美文网首页
二十九、iOS底层原理-自动释放池

二十九、iOS底层原理-自动释放池

作者: Mjs | 来源:发表于2020-11-27 13:37 被阅读0次

    @autoreleasepool编译分析

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
        }
        return 0;
    }
    

    xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m

    clang编译一下

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        }
        return 0;
    }
    
    struct __AtAutoreleasePool {
      __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
      ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
      void * atautoreleasepoolobj;
    };
    

    我们看到main中只有__AtAutoreleasePool __autoreleasepool;,全局搜索到结构体的构造函数和析构函数。

    我们可以看到源码上的注释

    Autorelease pool implementation
    A thread's autorelease pool is a stack of pointers. Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary. A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.
    The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.Thread-local storage points to the hot page, where newly autoreleased objects are stored.
    一个线程的自动释放池是一个指针堆栈。每个指针要么是一个要释放的对象,要么是一个自动释放池边界POOL_BOUNDARY。池标记是指向池的POOL_BOUNDARY的指针。当池爆炸时,所有温度高于哨兵的物体都会被释放。
    堆栈被划分为一个双链接的页面列表。根据需要添加和删除页面。线程本地存储指向热页,其中存储新自动释放的对象。

    查看objc_autoreleasePoolPush源码

    void *
    objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();
    }
    class AutoreleasePoolPage : private AutoreleasePoolPageData
    struct AutoreleasePoolPageData
    {
        magic_t const magic;
        __unsafe_unretained id *next;
        pthread_t const thread;
        AutoreleasePoolPage * const parent;
        AutoreleasePoolPage *child;
        uint32_t const depth;
        uint32_t hiwat;
    
        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)
        {
        }
    };
    

    这个是自动释放池数据页结构,可以看到是个双向链表。

    • magic ⽤来校验 AutoreleasePoolPage 的结构是否完整;
    • next 指向最新添加的 autoreleased 对象的下⼀个位置,初始化时指向begin() ;
    • thread 指向当前线程;
    • parent 指向⽗结点,第⼀个结点的 parent 值为 nil ;
    • child 指向⼦结点,最后⼀个结点的 child 值为 nil ;
    • depth 代表深度,从 0 开始,往后递增 1;
    • hiwat 代表 high water mark 最⼤⼊栈数量标记
        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();
        }
    
    id * begin() {
            return (id *) ((uint8_t *)this+sizeof(*this));//+56
        }
    

    AutoreleasePoolPage构造方法,当前线程通过tls获取出来,next赋值的为当前地址+56;
    我们关闭arc打印一下

    extern void _objc_autoreleasePoolPrint(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            for (int i=0; i<5; i++) {
                NSObject *objc = [[NSObject alloc] autorelease];
            }
    
            _objc_autoreleasePoolPrint();
    
        }
        return 0;
    }
    ·········
    objc[3690]: ##############
    objc[3690]: AUTORELEASE POOLS for thread 0x1000d2dc0
    objc[3690]: 6 releases pending.
    objc[3690]: [0x102003000]  ................  PAGE  (hot) (cold)
    objc[3690]: [0x102003038]  ################  POOL 0x102003038  哨兵对象  边界  防止越界
    objc[3690]: [0x102003040]       0x101842b30  NSObject
    objc[3690]: [0x102003048]       0x101801d20  NSObject
    objc[3690]: [0x102003050]       0x101841270  NSObject
    objc[3690]: [0x102003058]       0x101841450  NSObject
    objc[3690]: [0x102003060]       0x101840850  NSObject
    objc[3690]: ##############
    

    哨兵对象就是page开始的地方+page的大小。

    class AutoreleasePoolPage : private AutoreleasePoolPageData
    {
    ......
    
    public:
        static size_t const SIZE =
    #if PROTECT_AUTORELEASEPOOL
            PAGE_MAX_SIZE;  // must be multiple of vm page size
    #else
            PAGE_MIN_SIZE;  // size and alignment, power of 2
    #endif
    }
    
    #define PAGE_MAX_SIZE           PAGE_SIZE
    #define PAGE_SIZE               I386_PGBYTES
    #define I386_PGBYTES            4096 
    

    一页的大小为4096,减去当前page结构体大小56为4040,在减去哨兵对象8个字节4040,我们把for循环数值放大到505,输出打印结果,最后为

    objc[3825]: [0x102005ff0]       0x10140d120  NSObject
    objc[3825]: [0x102005ff8]       0x10140d130  NSObject
    objc[3825]: [0x102007000]  ................  PAGE  (hot) 
    objc[3825]: [0x102007038]       0x10140d140  NSObject
    objc[3825]: ##############
    

    自动释放池中只有一个哨兵对象,所以第二页开始没有哨兵对象,可以放505个对象。

    push

    我们接下来看他是如何压栈的。


    自动释放池.png
    0. 判断页是否存在
        static inline void *push() 
        {
            id *dest;
            if (slowpath(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 *page = hotPage();
            if (page && !page->full()) {//当页未满
                return page->add(obj);
            } else if (page) {//存在
                return autoreleaseFullPage(obj, page);
            } else {//不存在
                return autoreleaseNoPage(obj);
            }
        }
    
    1.添加当前页,变成hot。 压栈进来一个边界

    首先看看空的情况下

     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.
            AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    //设置聚焦页面
            setHotPage(page);
            
            // Push a boundary on behalf of the previously-placeholder'd pool.
            if (pushExtraBoundary) {
    //加入BOUNDARY
                page->add(POOL_BOUNDARY);
            }
            
            // Push the requested object or pool.
            return page->add(obj);
        }
    
    判断页是否存在,是否满了
        id *add(id obj)
        {
            ASSERT(!full());
            unprotect();
            id *ret = next;  // faster than `return next-1` because of aliasing
            *next++ = obj;
            protect();
            return ret;
        }
    

    先将next拷贝一下,再讲当前对象放在next++

    当前页面满了
        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);
    
            do {
                if (page->child) page = page->child;
                else page = new AutoreleasePoolPage(page);
            } while (page->full());
    
            setHotPage(page);
            return page->add(obj);
        }
    

    如果当前页面满了,就替换成子节点,如果没有子节点就生成新的页面,把当前页面设置为hot,再添加obj。

    pop

        pop(void *token)
        {
            AutoreleasePoolPage *page;
            id *stop;
    //是空的占位符
            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;
            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);
            }
    
            return popPage<false>(token, page, stop);
        }
    
        static void
        popPage(void *token, AutoreleasePoolPage *page, id *stop)
        {
            if (allowDebug && PrintPoolHiwat) printHiwat();
    
            page->releaseUntil(stop);
    
            // 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();
                }
            }
        }
    
    
        void releaseUntil(id *stop) 
        {
            // Not recursive: we don't want to blow out the stack 
            // if a thread accumulates a stupendous amount of garbage
            
            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
                while (page->empty()) {
                    page = page->parent;
                    setHotPage(page);
                }
    
                page->unprotect();
                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
        }
    

    先通过循环将当前页的节点一个个出栈,当前页面清空完毕,找到上一个页面,再一个个清空。

        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->protect();
                }
                delete deathptr;
            } while (deathptr != this);
        }
    

    找到最后页面,一层层向上清除

    autoreleasepool嵌套

        @autoreleasepool {
            
            NSString *name = [NSString stringWithString:@"1222222"];
            NSObject *objc1 = [[[NSObject alloc] init] autorelease];
             _objc_autoreleasePoolPrint();
             @autoreleasepool {
                 NSLog(@"*****1*******");
                 NSObject *objc2 = [[[NSObject alloc] init] autorelease];
                 NSObject *objc3 = [[[NSObject alloc] init] autorelease];
                 _objc_autoreleasePoolPrint();
                 @autoreleasepool {
                     NSObject *objc4 = [[[NSObject alloc] init] autorelease];
                     NSObject *objc5 = [[[NSObject alloc] init] autorelease];
                     NSObject *objc6 = [[[NSObject alloc] init] autorelease];
                     NSLog(@"****2********");
                     _objc_autoreleasePoolPrint();
                 }
             }
            NSLog(@"*****3*******");
            _objc_autoreleasePoolPrint();
         }
    
    ··············
    objc[2780]: ##############
    objc[2780]: AUTORELEASE POOLS for thread 0x1000d2dc0
    objc[2780]: 2 releases pending.
    objc[2780]: [0x105800000]  ................  PAGE  (hot) (cold)
    objc[2780]: [0x105800038]  ################  POOL 0x105800038
    objc[2780]: [0x105800040]       0x101e0b510  NSObject
    objc[2780]: ##############
    2020-11-28 14:25:21.788558+0800 KCObjc[2780:173966] *****1*******
    objc[2780]: ##############
    objc[2780]: AUTORELEASE POOLS for thread 0x1000d2dc0
    objc[2780]: 5 releases pending.
    objc[2780]: [0x105800000]  ................  PAGE  (hot) (cold)
    objc[2780]: [0x105800038]  ################  POOL 0x105800038
    objc[2780]: [0x105800040]       0x101e0b510  NSObject
    objc[2780]: [0x105800048]  ################  POOL 0x105800048
    objc[2780]: [0x105800050]       0x101917db0  NSObject
    objc[2780]: [0x105800058]       0x101918eb0  NSObject
    objc[2780]: ##############
    2020-11-28 14:25:21.789128+0800 KCObjc[2780:173966] ****2********
    objc[2780]: ##############
    objc[2780]: AUTORELEASE POOLS for thread 0x1000d2dc0
    objc[2780]: 9 releases pending.
    objc[2780]: [0x105800000]  ................  PAGE  (hot) (cold)
    objc[2780]: [0x105800038]  ################  POOL 0x105800038
    objc[2780]: [0x105800040]       0x101e0b510  NSObject
    objc[2780]: [0x105800048]  ################  POOL 0x105800048
    objc[2780]: [0x105800050]       0x101917db0  NSObject
    objc[2780]: [0x105800058]       0x101918eb0  NSObject
    objc[2780]: [0x105800060]  ################  POOL 0x105800060
    objc[2780]: [0x105800068]       0x101917860  NSObject
    objc[2780]: [0x105800070]       0x1019178a0  NSObject
    objc[2780]: [0x105800078]       0x10190cf60  NSObject
    objc[2780]: ##############
    2020-11-28 14:25:21.798077+0800 KCObjc[2780:173966] *****3*******
    objc[2780]: ##############
    objc[2780]: AUTORELEASE POOLS for thread 0x1000d2dc0
    objc[2780]: 2 releases pending.
    objc[2780]: [0x105800000]  ................  PAGE  (hot) (cold)
    objc[2780]: [0x105800038]  ################  POOL 0x105800038
    objc[2780]: [0x105800040]       0x101e0b510  NSObject
    objc[2780]: ##############
    

    每次 Push 后,都会先添加一个 POOL_BOUNDARY 来占位,是为了对应一次 Pop 的释放,图中表示了两层autoreleasepool的嵌套,需要两次pop完全释放。
    autoreleasepool使用双链表来实现,只有在当前 page 空间使用完后,才会创建新的 page,并不是每个 @autoreleasepool 对应一个 AutoreleasePoolPage 对象。
    没有POOL_BOUNDARY的AutoreleasePoolPage对象一次pop会释放当前结点中的autorelease对象,下一次pop从父节点开始


    autoreleasepool.png

    相关文章

      网友评论

          本文标题:二十九、iOS底层原理-自动释放池

          本文链接:https://www.haomeiwen.com/subject/awwtwktx.html