• iOS——Autoreleasepool底层原理


    什么是autoreleasepool

    顾名思义,autoreleasepool也称为自动释放池,类似于C语言中的自动变量,我们可以将对象加入到autoreleasepool中,当其超出其作用域时,调用release释放该对象。
    在之前探究ARC实现时,我们可以发现ARC将非自己持有的对象加入到了autoreleasepool。
    在这里插入图片描述
    下面深入了解一下autoreleasepool的结构

    autoreleasepool的结构

    在objc中查看源码可以发现最后会走到:
    在这里插入图片描述
    上面的方法看上去是对 AutoreleasePoolPage 对应静态方法 push 和 pop 的封装。下面看看这个AutoreleasePoolPage。

    AutoreleasePoolPage

    在objc中点开其源码:

    // MARK: - AutoReleasepool class
    class AutoreleasePoolPage 
    {
        // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
        // pushed and it has never contained any objects. This saves memory 
        // when the top level (i.e. libdispatch) pushes and pops pools but 
        // never uses them.
    #   define EMPTY_POOL_PLACEHOLDER ((id*)1)
    
    #   define POOL_BOUNDARY nil
        static pthread_key_t const key = AUTORELEASE_POOL_KEY;
        static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
        static size_t const SIZE = 
    #if PROTECT_AUTORELEASEPOOL
            PAGE_MAX_SIZE;  // must be multiple of vm page size
    #else
            PAGE_MAX_SIZE;  // size and alignment, power of 2
    #endif
        static size_t const COUNT = SIZE / sizeof(id);
    
        // 校验完整性
        magic_t const magic;
        
        // next指针指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()
        id *next;
        
        // 指向当前线程
        pthread_t const thread;
        
        // 父节点
        AutoreleasePoolPage * const parent;
        
        // 子节点
        AutoreleasePoolPage *child;
        
        // 节点深度
        uint32_t const depth;
        uint32_t hiwat;
    
        // SIZE-sizeof(*this) bytes of contents follow
    
        static void * operator new(size_t size) {
            return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
        }
        static void operator delete(void * p) {
            return free(p);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    这个数据结构很熟悉,原来Auto releasePool的底层是由一系列的 AutoreleasePoolPage 组成的
    (并且每一个 AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000))

    #define I386_PGBYTES 4096
    #define PAGE_SIZE I386_PGBYTES
    
    • 1
    • 2

    结构如下所示:
    在这里插入图片描述
    是一个双向链表,parent 和 child 就是用来构造双向链表的指针。

    在官方文档对于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.
    每个指针都是要释放的对象,或者是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.
    池令牌是指向该池的POOL_BOUNDARY的指针。弹出池后,将释放比哨点更热的每个对象。
     
    - 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    1、自动释放池 是一个 关于指针的栈结构

    2、其中的指针是指要释放的对象或者 pool_boundary 哨兵(现在经常被称为 边界)
    pool_boundary:POOL_BOUNDARY:nil的宏定义,替代之前的哨兵对象POOL_SENTINEL,在自动释放池创建时,在objc_autoreleasePoolPush中将其推入自动释放池中。在调用objc_autoreleasePoolPop时,会将池中对象按顺序释放,直至遇到最近一个POOL_BOUNDARY时停止。

    3、自动释放池是一个页的结构(虚拟内存中提及过) ,而且这个页是一个双向链表(表示有父节点 和 子节点,在类中提及过,即类的继承链)

    4、一些名词

    • hotPage:是当前正在使用的page,操作都是在hotPage上完成,一般处于链表末端或者倒数第二个位置。存储在 TLS 中,可以理解为一个每个线程共享一个自动释放池链表。

    • coldPage:位于链表头部的page,可能同时为hotPage

    • EMPTY_POOL_PLACEHOLDER:当自动释放池中没有推入过任何对象时,这个时候推入一个POOL_BOUNDARY,会先将EMPTY_POOL_PLACEHOLDER存储在 TLS 中作为标识符,并且此次并不推入POOL_BOUNDARY。等再次有对象被推入自动释放池时,检查在 TLS 中取出该标识符,这个时候再推入POOL_BOUNDARY

    • next:指向AutoreleasePoolPage指向栈顶空位的指针,每次加入新的元素都会往上移动。

    autoreleasePool中的栈

    如果我们的一个 AutoreleasePoolPage 被初始化在内存的 0x100816000 ~ 0x100817000 中,它在内存中的结构如下:
    在这里插入图片描述
    其中有 56 bit 用于存储 AutoreleasePoolPage 的成员变量,剩下的 0x100816038 ~ 0x100817000 都是用来存储加入到自动释放池中的对象。
    begin() 和 end() 这两个类的实例方法帮助我们快速获取 0x100816038 ~ 0x100817000 这一范围的边界地址。
    next 指向了下一个为空的内存地址,如果 next 指向的地址加入一个 object,它就会如下图所示移动到下一个为空的内存地址中:
    在这里插入图片描述
    在栈中release加入的元素时候,就会根据哨兵也就是pool_boundary来作为边界Pop。
    当方法 objc_autoreleasePoolPop 调用时,就会向自动释放池中的对象发送 release 消息,直到第一个 POOL_SENTINEL:
    在这里插入图片描述

    autoreleasePool的实现源码

    objc_autoreleasePoolPush

    void *
    objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    它调用 AutoreleasePoolPage 的类方法 push,也非常简单:

    // push 返回的是next指针指向的obj(是一个released obj)
        static inline void *push() 
        {
            id *dest;
            if (DebugPoolAllocation) {
                
                // 每一个autoreleasepool对象开始于一个新的autoreleasepoolpage
                // 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;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    objc_autoreleasePoolPush方法其实就是向自动释放池推入一个POOL_BOUNDARY,作为该autoreleasepool的起点。autoreleaseFast方法的具体逻辑将在后面分析autorelease方法时再进行分析。

    autorelease

    下面看加入自动释放池方法autorelease的代码实现:

    static inline id autorelease(id obj) {
            id *dest __unused = autoreleaseFast(obj);
            return obj;
    }
    
    • 1
    • 2
    • 3
    • 4

    可以看到这里把对象作为参数调用了autoreleaseFast

     // 创建一个page逻辑
        static inline id *autoreleaseFast(id obj)
        {
            // hotPage可以理解为正在使用的AutoreleasePoolPage
            AutoreleasePoolPage *page = hotPage();
            if (page && !page->full()) {
                // 有 hotPage 且不满 直接加入栈中
                return page->add(obj);
            } else if (page) {
                // hotPage 已满 先创建一个 Page 后,加入新 Page 中
                return autoreleaseFullPage(obj, page);
            } else {
                // 没有 hotPage 直接新建一个 Page,并加入 Page 中
                return autoreleaseNoPage(obj);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这段代码的逻辑:

    • 如果HotPage存在且未满,则直接推入hotPage。
    • 如果HotPage存在且已经满,则调用autoreleaseFullPage,去初始化一个新的页,并推入
    • 如果不存在HotPage,则调用autoreleaseNoPage,创建一个hotPage,推入hotPage。

    autoreleaseFullPage(当前 hotPage 已满)

     // MARK: - 创建一个new page
        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 ,设置成 hotPage
            do {
                if (page->child) page = page->child;
                else page = new AutoreleasePoolPage(page);
            } while (page->full());
    
            setHotPage(page);
            return page->add(obj);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    该方法逻辑如下:

    • 查看hotPage是否有后继节点(page),如果有直接使用后继节点。
    • 如果没有后继节点,则新建一个AutoreleasePoolPage。
    • 将对象加入获取到的page,并将其设置为hotPage,其实就是存入 TLS 中共享。

    autoreleaseNoPage(没有 hotPage)

    // autoreleasepool中没有page
        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) {//如果没有创建一个autoreleasepool,则直接抛异常
                // 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", 
                             pthread_self(), (void*)obj, object_getClassName(obj));
                objc_autoreleaseNoPool(obj);
                return nil;
            }
            else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) { // 如果传入 POOL_BOUNDARY 则设置空池占位符
                // 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.
            // 初始化一个 page 并设置 hotPage
            // Install the first page.
            AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
            setHotPage(page);
            
            // 插入释放池边界
            if (pushExtraBoundary) {
                page->add(POOL_BOUNDARY);
            }
            
            //  obj添加到page中
            return page->add(obj);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    autoreleaseNoPage只有在自动释放池还没有page时调用,主要逻辑:

    • 如果当前自动释放池推入的是一个哨兵POOL_BOUNDARY时,将EmptyPoolPlaceholder存入 TLS 中。
    • 如果 TLS 存储了EmptyPoolPlaceholder时,在创建好page之后,会先推入一个POOL_BOUNDARY,然后再将加入自动释放池的对象推入。
      推入都是调用了add函数:
    // 向page中加入对象
        id *add(id obj)
        {
            assert(!full());
            unprotect();
            
            // 将next节点赋值给ret
            id *ret = next;  // faster than `return next-1` because of aliasing
            
            // 将obj赋值给next
            *next++ = obj;
            protect();
            return ret;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这个很好理解,就是一个压入栈的操作。

    objc_autoreleasePoolPop

    在自动释放池所在作用域结束时,会调用objc_autoreleasePoolPop,对自动释放池中的对象进行释放。

    // pop
        static inline void pop(void *token) 
        {
            AutoreleasePoolPage *page;
            id *stop;
    
            // 如果是空池占位符,要清空整个自动释放池
            if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
                // Popping the top-level placeholder pool.
                if (hotPage()) {
                    // Pool was used. Pop its contents normally.
                    // Pool pages remain allocated for re-use as usual.
                    // 如果存在 hotPage ,则找到 coldPage 的起点 重新 pop
                    pop(coldPage()->begin());
                } else {
                    // 未使用过的释放池,置空 TLS 中存放的 hotPage.
                    setHotPage(nil);
                }
                return;
            }
    
            // 根据token获取page
            page = pageForPointer(token);
            stop = (id *)token;
            if (*stop != POOL_BOUNDARY) {
                // 在 stop 不为 POOL_BOUNDARY 的情况下 只可能是 coldPage()->begin()
                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 {
                    // 如果不是 POOL_BOUNDARY 也不是 coldPage()->begin() 则报错
                    // Error. For bincompat purposes this is not 
                    // fatal in executables built with old SDKs.
                    return badPop(token);
                }
            }
    
            if (PrintPoolHiwat) printHiwat();
    
            /// 释放 stop 后面的所有对象
            page->releaseUntil(stop);
    
            // 清除空节点
            if (DebugPoolAllocation  &&  page->empty()) {
                // special case: delete everything during page-per-pool debugging
                AutoreleasePoolPage *parent = page->parent;
                page->kill();
                setHotPage(parent);
            } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
                // special case: delete everything for pop(top) 
                // when debugging missing autorelease pools
                page->kill();
                setHotPage(nil);
            }
            // 清除后续节点 page
            else if (page->child) {
                // 如果当前 page 没有达到半满,则干掉所有后续 page
                if (page->lessThanHalfFull()) {
                    page->child->kill();
                }
                // 如果当前 page 达到半满以上,则保留下一页
                else if (page->child->child) {
                    page->child->child->kill();
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    这段代码逻辑:

    • 检查入参是否为空池占位符EMPTY_POOL_PLACEHOLDER,如果是则继续判断是否hotPage存在,如果hotPage存在则将释放的终点改成coldPage()->begin(),如果hotPage不存在,则置空 TLS 存储中的hotPage
    • 检查stop既不是POOL_BOUNDARY也不是coldPage()->begin()的情况将报错。
    • 清空自动释放池中stop之后的所有对象。
    • 判断当前page如果没有达到半满,则干掉所有后续所有 page,如果超过半满则只保留下一个page。

    这里用到了几个函数,首先是通过token获取page的pageForPointer:

     static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
        {
            AutoreleasePoolPage *result;
            uintptr_t offset = p % SIZE;
    // 将指针与页面的大小,也就是 4096 取模,得到当前指针的偏移量,因为所有的 AutoreleasePoolPage 在内存中都是对齐的:
            assert(offset >= sizeof(AutoreleasePoolPage));
    
            result = (AutoreleasePoolPage *)(p - offset);
            result->fastcheck();
    
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    将指针与页面的大小,也就是 4096 取模,得到当前指针的偏移量,因为所有的 AutoreleasePoolPage 在内存中都是对齐的:

    p = 0x100816048
    p % SIZE = 0x48
    result = 0x100816000
    而最后调用的方法 fastCheck() 用来检查当前的 result 是不是一个 AutoreleasePoolPage。

    通过检查 magic_t 结构体中的某个成员是否为 0xA1A1A1A1。

    releaseUntil 释放对象

    实现如下:

     // MARK: - 释放obj
        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
                
                // 获取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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在这里通过一个while循环,判断当前page如果被清空,则继续清理链表中的上一个page,release了page里面存储的对象。

    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);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    大概逻辑就是先找到最后一页,然后一直删直到删到当前页。

    autorelease的调用栈

    在这里插入图片描述
    首先就是我们创建autoreleasePool,就是从autoreleasePoolPush开始到Pop结束。

    - [NSObject autorelease]
    └── id objc_object::rootAutorelease()
        └── id objc_object::rootAutorelease2()
            └── static id AutoreleasePoolPage::autorelease(id obj)
                └── static id AutoreleasePoolPage::autoreleaseFast(id obj)
                    ├── id *add(id obj)
                    ├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
                    │   ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                    │   └── id *add(id obj)
                    └── static id *autoreleaseNoPage(id obj)
                        ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                        └── id *add(id obj)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    autorelease方法的调用栈中,最终都会调用上面提到的 autoreleaseFast 方法,将当前对象加到 AutoreleasePoolPage 中。

    RunLoop和autoreleasePool

    iOS在主线程的RunLoop中注册了两个Observer:

    • 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush();
    • 第2个Observer:
      ① 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush();
      ② 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()。
  • 相关阅读:
    JavaWeb基础
    Android RecyclerView使用ListAdapter高效刷新数据
    CSS3 新特性
    [H5动画制作系列] Sprite及Text Demo
    JavaScript 前端枚举库 js-enumerate
    TreeSet和HashSet
    第07章 连接Hadoop集群
    毕业设计之基于Spring+SpringMVC+Mybatis分布式敏捷开发系统架构
    [SECCON CTF 2022] 只两个小题pwn_koncha,rev_babycmp
    CatBoost高级教程:分布式训练与大规模数据处理
  • 原文地址:https://blog.csdn.net/chabuduoxs/article/details/126326184