美文网首页
iOS内存管理4-autorelease自动释放池

iOS内存管理4-autorelease自动释放池

作者: 东旭39 | 来源:发表于2021-03-16 09:13 被阅读0次

    临时变量什么时候释放
    自动释放池的原理
    自动释放池能否嵌套使用

    使用xcode创建一个project在创建的时候有会生成一个main.m文件,其中@autoreleasePool的注解
    可以通过clang将main.m转义成C++文件,找到源码切入

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

    打开生成的main.cpp文件

    
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        //自动释放池的作用域
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
        }
        return UIApplicationMain(argc, argv, __null, appDelegateClassName);
    }
    
    
    struct __AtAutoreleasePool {
      __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
      ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
      void * atautoreleasepoolobj;
    };
    

    通过转义后的代码autorelease是一个结构体,结构体变量atautoreleasepoolobj是通过objc_autoreleasePoolPush创建而来,他的作用域在一个{}内。

    另外有可以在在@autorelease处打断点,进入汇编调试界面,可以看到注释调用了objc_autoreleasePoolPush方法。
    上面我们通过代码转义和汇编两个方式找到进入@autorelease源码的切入点

    Autorelease的官方说明

    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.

    1.指针->栈的结构

    1. 释放对象+POOL_BOUNDARY(边界或者叫哨兵)
    2. 页的结构 + 双向链表
    3. 线程相关

    自动释放池使用页,当前正在添加待释放对象的page称为hotpage,已经添加满了的page称为 coldpage。

    push

    通过objc4-781源码
    objc_autoreleasePoolPush->AutoreleasePoolPage::push()
    其中AutoreleasePoolPage是继承自 AutoreleasePoolPageData的类

    struct AutoreleasePoolPageData
    {
        magic_t const magic;   //用来校验AutoreleasePoolPage的结构是否完整 ,16字节(静态变量不占用结构体内存,它的大小是 uint32_5 m[4] = 4*4 的大小)
        __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开始,4字节
        uint32_t hiwat;          //代表high water mark 最大入栈数量标记,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)
        {
        }
    };
    

    AutoreleasePoolPage对象初始化

        AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
            AutoreleasePoolPageData(begin(),
                                    objc_thread_self(),
                                    newParent,
                                    newParent ? 1+newParent->depth : 0,
                                    newParent ? newParent->hiwat : 0)
        {
            if (objc::PageCountWarning != -1) {
                checkTooMuchAutorelease();
            }
    
            if (parent) {
                parent->check();
                ASSERT(!parent->child);
                parent->unprotect();
                parent->child = this;
                parent->protect();
            }
            protect();
        }
    

    其中begin()是当前对象的起始地址+ 56个字节,56个字节是AutoreleasePoolPageData中的变量已经占用了这么多,那么一个存放对象的地址应该是(id *) ((uint8_t *)this+sizeof(*this));

    通过源码调试,需要切换到MRC环境

    #import <Foundation/Foundation.h>
    //自动释放池的打印方法
    extern void _objc_autoreleasePoolPrint(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
          
            for (int i = 0; i<10; i++) {
                NSObject *objc = [[NSObject alloc] autorelease];
            }
            _objc_autoreleasePoolPrint();
        }
        return 0;
    }
    
    

    输出


    image.png

    在释放池中添加505个对象

    image.png
    自动释放池创建了新的一页,在0x10180d038位开始的是NSObject对象,不是哨兵对象。
    在x86架构下,第一页结构是page的原始结构+504个释放对象 +哨兵对象,大小是(504 + 1)*8 +56 = 4096 = 4kb
    从第二页开始没有哨兵对象,一共可以存放505个释放对象

    AutoreleasePoolPage::push()源码

        static inline void *push() 
        {
            id *dest;
            //执行slowpath概率很小
            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;
        }
    

    autoreleaseNewPage()创建一个新的page

        static __attribute__((noinline))
        id *autoreleaseNewPage(id obj)
        {  
            //1.获取聚焦page
            AutoreleasePoolPage *page = hotPage();
            //2.存在hotpage就添加
            if (page) return autoreleaseFullPage(obj, page);
            //3.没有hotpage就表明需要创建一个page
            else return autoreleaseNoPage(obj);
        }
    

    autoreleaseFast源码

        static inline id *autoreleaseFast(id obj)
        {
            AutoreleasePoolPage *page = hotPage();
          //1.有hotpage,切没有满,直接添加
            if (page && !page->full()) {
                return page->add(obj);
            } else if (page) {
                //2.如果页满了
                return autoreleaseFullPage(obj, page);
            } else {
              //3.没有获取到page
                return autoreleaseNoPage(obj);
            }
        }
    

    push的流程图如下

    image.png

    但是由于slowpath的关系,执行autoreleaseNewPage概率很小。

    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);
            //如果hotpage满了,获取下一个非full的页给当前page,跳出循环后,设置当前page为hotpage,并将随访对象添加
            do {
                if (page->child) page = page->child;
                else page = new AutoreleasePoolPage(page);
            } while (page->full());
    
            setHotPage(page);
            return page->add(obj);
        }
    
    • 1.如果hotpage满了,判断是否有child,
      • 1.1没有child就创建一个page,复制给当前page,并退出当前会还
      • 1.2有就将child复制给当前page,继续while循环判断是否满了
    • 2.page没有满,跳出循环,设置当前page为hotpage,
    • 3.将释放对象add到page中

    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
          //No page 可能是没有被东西被压入栈中,或者只有一个占位的哨兵
            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) {
                page->add(POOL_BOUNDARY);
            }
            
            // Push the requested object or pool.
             //将释放对象加入page中
            return page->add(obj);
        }
    
    • 1.判断是否是空占位符,如果是,则压栈哨兵标识符置为YES
    • 2.如果对象不是哨兵对象,且没有Pool,则报错
    • 3.如果对象是哨兵对象,且没有申请自动释放池内存,则设置一个空占位符存储在tls中,其目的是为了节省内存
    • 4.初始化第一页,并设置为hotpage
    • 5.压栈哨兵的标识符为YES,则压栈哨兵对象
    • 6.压栈对象
      add方法
        id *add(id obj)
        {
            ASSERT(!full());
            unprotect();
            id *ret = next;  // faster than `return next-1` because of aliasing
            //将obj压栈到next指针位置,然后next进行++,即下一个对象存储的位置
            *next++ = obj;
            protect();
            return ret;
        }
    
    

    autolease:pop

    当程序运行出自动释放池的作用域范围会__AtAutoreleasePool调用c++的析构函数,将pop出栈方法写入析构函数,开始释放。

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

    pop->popPage->releaseUntil

    static inline void
        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 {
             
                    return badPop(token);
                }
            }
    
            if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
                return popPageDebug(token, page, stop);
            }
    
            return popPage<false>(token, page, stop);
        }
    

    popPage之前的代码都是在进行容错处理,page = pageForPointer(token);是根据token获取page。

        template<bool allowDebug>
        static void
        popPage(void *token, AutoreleasePoolPage *page, id *stop)
        {
           //此时传入的allowDebug为false
            if (allowDebug && PrintPoolHiwat) printHiwat();
           //出栈当前page的对象
            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, 如果page超过一半,保存一个空的子集
                if (page->lessThanHalfFull()) {
                    page->child->kill();
                }
                else if (page->child->child) {
                    page->child->child->kill();
                }
            }
        }
    

    page->releaseUntil(stop);

     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循环
            while (this->next != stop) {
                // Restart from hotPage() every time, in case -release 
                // autoreleased more objects
              //获取当前操作页面,即hot页面
                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();
                //next进行--操作,即出栈
                id obj = *--page->next;
              //将page的索引位设置为SCRIBBLE,表示已经被释放
                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;
            //1.获取最后位置的page
            while (page->child) page = page->child;
    
            AutoreleasePoolPage *deathptr;
            do {
                deathptr = page;
              //将父parent赋值给当前page
                page = page->parent;
                if (page) {
                    page->unprotect();
                    //产出最后个page
                    page->child = nil;
                    page->protect();
                }
                delete deathptr;
            } while (deathptr != this);   //最后一个page不等于当前page
        }
    

    相关文章

      网友评论

          本文标题:iOS内存管理4-autorelease自动释放池

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