• 分析自动释放池aureleasePool的原理


    一、简介

    aureleasePool,顾名思义,自动释放池。它在iOS系统的内存管理中,有着极其重要的作用。放入其池中的对象,最终系统通过它进行释放,不用程序员手动去管理。在MRC中,当然还是需要手动调用一个autorelase方法将对象添加进自动释放池,在ARC中,这一步直接省略,只需要在 @autoreleasepool { ... }中创建对象,然后执行代码即可。最后,在对象需要释放的时候,自动释放池自动帮助完成这一行为。

    二、概念

    自动释放池到底是什么?其实,在对源码进行分析的过程中,发现它是由双向链表构成的,通过栈来管理对象。一句话总结就是,它是以栈为节点通过双向链表实现的一种存储结构。

    三、示例

    说了这么多,那么放入自动释放池的对象,到底是不是由它来管理内存的呢?现在来验证一下,看看到底是不是由它来释放的,示例代码如下:main.m

    #import <Foundation/Foundation.h>
    
    __weak NSObject *weak_obj1; 
    __weak NSObject *weak_obj2;
    
    int main(int argc, const char * argv[]) {
        
        NSObject *obj1 = [[NSObject alloc] init];
        weak_obj1 = obj1;
        NSLog(@"before---weak_obj1--%p",weak_obj1);
        
        @autoreleasepool {
            NSObject *obj2 = [[NSObject alloc] init];
            weak_obj2 = obj2;
            NSLog(@"before---weak_obj2--%p",weak_obj2);
        }
        
        NSLog(@"after---weak_obj1--%p",weak_obj1);
        NSLog(@"after---weak_obj2--%p",weak_obj2);
        
        return 0;
    }

    创建了两个weak指针分别指向对象obj1和ob2,obj1对象在自动释放池之外创建的,obj2对象则是在自动释放池之内创建的。按照上面简介的说明,猜想打印的结果一定是:

    • obj1 在 before 和 after 这里,指针的值一直存在,除非到程序执行结束,也即main函数执行完毕,它才被系统释放回收。
    • obj2 在 before 这里,指针的值是存在的,但是出了自动释放池的作用域外面后,在after 这里, 指针的值一定为空,表明它由自动释放池进行释放回收了。

    打印结果,果然印证了这个猜想,如下所示:

    2021-04-16 17:11:04.250446+0800 原理剖析[82323:4507357] before---weak_obj1--0x1031adbe0
    2021-04-16 17:11:04.250835+0800 原理剖析[82323:4507357] before---weak_obj2--0x103304830
    2021-04-16 17:11:04.250887+0800 原理剖析[82323:4507357] after---weak_obj1--0x1031adbe0
    2021-04-16 17:11:04.250917+0800 原理剖析[82323:4507357] after---weak_obj2--0x0
    Program ended with exit code: 0

    四、结构

    结果是喜人的,接着,我们就需要去一探究竟了。它的结构是什么的?如何实现的? 为了更好更直观的分析结构,我们需要做一些前期工作,就是把OC代码转成C++代码。xcode自带了clang编译器,可以借助它进行语法的转换。转换命令行如下:

    /*
    xcrun: xcode run运行。
    
    -sdk:表示转成什么平台下的C++代码。例如 iphoneos,代表手机系统,可以是模拟器,也可以是真机。如果不写,会兼容所有的平台,macos、iphoneos、windowos。
    
    clang:编译器
    
    -arch:表示在选择的此平台下,采用什么样的架构。arm64是真机64bit的,i386是模拟器32bit的。如果不写,会兼容该平台下的所有架构。
    
    -fobjc-arc:表示内存的管理方式,可以不用写,默认ARC。
    
    -fobjc-runtime=ios-8.0.0:weak指针修饰的变量,需要低系统环境支持,此处指定iOS8.0。
    
    -o:output 输出文件
    */
    
    
    // weak 需要运行时环境的支持。当你选择了一个合适的最低版本后,才能编译有 weak 修饰变量的源码。
    
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o mian_arm64.cpp 
    
    xcrun clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o mian.cpp  //【忽略:64 warnings generated.】
    
    
    // 非weak 修饰变量的源码。
    
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mian_arm64.cpp
    
    xcrun clang -rewrite-objc  main.m -o mian.cpp  //【忽略:64 warnings generated.】

    可以看到,不同的配置选择,生成c++代码文件大小是不一样的,如下所示:

    选择main_arm64.cpp文件点击查看,代码如下,可以发现,在自动释放池那里,其实是生成了一个代码块,内部创建了一个自动释放池对象_autorelasepool,它的类型是__AtAutoreleasePool。

    #pragma clang assume_nonnull end
    
    __attribute__((objc_ownership(weak))) NSObject *weak_obj1;
    __attribute__((objc_ownership(weak))) NSObject *weak_obj2;
    
    int main(int argc, const char * argv[]) {
    
        NSObject *obj1 = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        weak_obj1 = obj1;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_62_ptthn0jn271412kr3p6_0d3h0000gp_T_main_d51538_mi_0,weak_obj1);
    
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            NSObject *obj2 = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
            weak_obj2 = obj2;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_62_ptthn0jn271412kr3p6_0d3h0000gp_T_main_d51538_mi_1,weak_obj2);
        }
    
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_62_ptthn0jn271412kr3p6_0d3h0000gp_T_main_d51538_mi_2,weak_obj1);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_62_ptthn0jn271412kr3p6_0d3h0000gp_T_main_d51538_mi_3,weak_obj2);
    
        return 0;
    }
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

    全局搜索这个__AtAutoreleasePool,发现它就是一个struct结构体,内部有一个构造函数、一个析构函数。创建时调用objc_autoreleasePoolPush函数,销毁时调用objc_autoreleasePoolPop函数。

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

    了解了这个结构后,我们来稍微改造一下当前的这个main_arm64.cpp函数,在大括号起始位置“{”处调用push函数,在大括号结束位置“}”处调用pop函数,更加清晰直观,如下所示:

    #pragma clang assume_nonnull end
    
    __attribute__((objc_ownership(weak))) NSObject *weak_obj1;
    __attribute__((objc_ownership(weak))) NSObject *weak_obj2;
    
    int main(int argc, const char * argv[]) {
    
        NSObject *obj1 = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        weak_obj1 = obj1;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_62_ptthn0jn271412kr3p6_0d3h0000gp_T_main_d51538_mi_0,weak_obj1);
    
        /* @autoreleasepool */  // { __AtAutoreleasePool __autoreleasepool;
            
            void * atautoreleasepoolobj = objc_autoreleasePoolPush(); 
            
            NSObject *obj2 = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
            weak_obj2 = obj2;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_62_ptthn0jn271412kr3p6_0d3h0000gp_T_main_d51538_mi_1,weak_obj2);
            
            objc_autoreleasePoolPop(atautoreleasepoolobj)
        //}
    
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_62_ptthn0jn271412kr3p6_0d3h0000gp_T_main_d51538_mi_2,weak_obj1);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_62_ptthn0jn271412kr3p6_0d3h0000gp_T_main_d51538_mi_3,weak_obj2);
    
        return 0;
    }
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

    五、源码

    现在需要去runtime源码中,看看 objc_autoreleasePoolPush函数 和 objc_autoreleasePoolPop函数的实现了,核心代码如下:

    //push入栈
    void * objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();
    }
    
    //pop出栈
    NEVER_INLINE void objc_autoreleasePoolPop(void *ctxt)
    {
        AutoreleasePoolPage::pop(ctxt);
    }

    从上面的方法实现,可以得出结论,自动释放池的实现其实是依赖类AutoreleasePoolPage的。AutoreleasePoolPage继承自结构体AutoreleasePoolPageData,也即

    class AutoreleasePoolPage : private AutoreleasePoolPageData { 
    
      static size_t const SIZE = PAGE_MIN_SIZE; //每一个page最大容量是4096字节
      .........
    
    }

    所以,接着AutoreleasePoolPageData的最终内部构成,可以看到,有父亲parent page,也有孩子child page,还有深度depth,说明它是个双向链表。里面还有一个next指针,可自增自减,进行存放位置的读取,是一个指针栈,如下所示:

    struct AutoreleasePoolPageData
    {
        magic_t const magic; //用来校验AutoreleasePoolPage的结构是否完整
    
        __unsafe_unretained id *next; //指向下一个即将产生的autoreleased对象的存放位置(当next == begin()时,表示AutoreleasePoolPage为空;当next == end()时,表示AutoreleasePoolPage已满。
    
        pthread_t const thread; //指向当前线程,一个AutoreleasePoolPage只会对应一个线程,但一个线程可以对应多个AutoreleasePoolPage;
    
        AutoreleasePoolPage * const parent;//指向父结点,第一个结点的 parent 值为 nil;
    
        AutoreleasePoolPage *child;//指向子结点,最后一个结点的 child 值为 nil
    
        uint32_t const depth; //代表深度,第一个page的depth为0,往后每递增一个page,depth会加1;
    
        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)
        {
        }
    };    

    对AutoreleasePoolPage有了基本认识后,现在深入分析一下它的push函数。继续,分析源代码如下。如果是debug模式下,每个自动释放池从一个新的池页开始;否则,执行快速释放池逻辑。其中,都传入了一个参数POOL_BOUNDARY,翻译为池边界,在这里则表示为哨兵对象,用来作为page存储对象的起始边界条件。

    //哨兵入栈
    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; }

    单个autoreleasepool就只有一个哨兵对象,多个嵌套autoreleasepool,就有多个哨兵对象。借用另一位博主的图例如下,  abc为图一,单个释放池。d为图二,嵌套释放池。

               (a)                                         (b)                                         (c)                                           (d)

    在自动释放池创建的对象,系统会自动给每一个对象发送autorelease消息,这样它们就可以被添加到自动释放池。存入自动释放池的对象地址在哨兵对象后面依次递增,最终释放对象的时候,系统会给每一个对象再次发送release消息进行释放,直到遇到哨兵对象后停止,表明对象全部释放完全。

    // 对象进自动释放池
    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;
    }

    autoreleaseNewPage函数 和 autoreleaseFast函数,它们的实现存在较多相似地方,我们直接去看对应的实现,代码分析如下。

    • 首先获取当前的page栈节点,如果存在且未填满,则直接将对对象添加到此page栈中;
    • 如果存在但是已经填满,则去寻找它的子page栈,当子page栈不存在的时候,就新建一个子page栈并设置为当前page栈,接着将对对象添加到此page栈中;
    • 如果不存在,则懒加载新建一个子page栈并设置为当前page栈,这里会通过haveEmptyPoolPlaceholder函数判断一下是否存在嵌套的自动释放池,然后决定添加新的哨兵对象,最后接着将对象添加到此page栈中。
    // 快速处理
    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); } }
    // 满页处理
    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); }
    // 懒加载处理
    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. 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. return page->add(obj); } // 新创建栈页 static __attribute__((noinline)) id *autoreleaseNewPage(id obj) { AutoreleasePoolPage *page = hotPage(); if (page) return autoreleaseFullPage(obj, page); else return autoreleaseNoPage(obj); }

    不难发现,将对象入栈的最终调用的都是add函数,代码比较简单,就是栈指针移动,将对象保存到指定栈中位置,如下所示:

    id *add(id obj)
    {
         ASSERT(!full());
         unprotect();
         id *ret = next;  // faster than `return next-1` because of aliasing
          *next++ = obj;
          protect();
          return ret;
    }

    同样地,将对象出栈进行释放,调用的是releaseUntil函数。从后往前遍历父page栈,直到位置为stop时才结束遍历。遍历的过程中,每从栈中取出一个obj对象,next指针递减,同时,只要该obj对象不是哨兵对象,就发送objc_release消息,进行释放。

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

    objc_release调用到runtime中,如下所示,最终结束流程。

    __attribute__((aligned(16), flatten, noinline))
    void 
    objc_release(id obj)
    {
        if (!obj) return;
        if (obj->isTaggedPointer()) return;
        return obj->release();
    }
    inline void
    objc_object::release()
    {
        ASSERT(!isTaggedPointer());
    
        if (fastpath(!ISA()->hasCustomRR())) {
            rootRelease();
            return;
        }
    
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
    }

    还有最后一点,push是将哨兵入栈,那哨兵对象出栈怎么实现的呢。其实,对应的就是pop函数。先调用releaseUntil函数将所有的非哨兵对象出栈,然后再将哨兵对象出栈。最终所有page均指向nil。代码如下:

    //pop出栈
    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 {
                // 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);
    }
    
    //先处理非哨兵对象,再处理哨兵对象
    template<bool allowDebug>
    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 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);
    }

     完结。

     

     

     

    程序猿神奇的手,每时每刻,这双手都在改变着世界的交互方式!
  • 相关阅读:
    jsp文件导包 糖不苦
    java 数字和日期处理 糖不苦
    java时间的一些处理 糖不苦
    Vimdiff 使用
    C++资源不完全索引
    sprintf 函数详细解释
    C++ 时间
    C++的可移植性和跨平台开发
    Solaris下开发64位程序的注意事项
    C/C++ 数学库函数
  • 原文地址:https://www.cnblogs.com/XYQ-208910/p/14668153.html
Copyright © 2020-2023  润新知