美文网首页iOS随记
从Runtime源代码解读内存管理机制——Autorelease

从Runtime源代码解读内存管理机制——Autorelease

作者: Luminix | 来源:发表于2019-07-21 01:57 被阅读0次

    2019-07-10

    一、概述

    上一篇文章 从Runtime源代码解读内存管理机制——Retain/Release,学习了 Objective-C 的引用计数内存管理的实现机制,本文继续探究另一种内存管理方式 Autoreleasing,也就是通过自动释放池 autorelease pool 进行内存管理。以下摘自 Runtime 源代码中名为 Autorelease pool implementation 的注释:

    A thread's autorelease pool is a stack of pointers.
    Each pointer is either an object to release, or POOL_SENTINEL which is
    an autorelease pool boundary.
    A pool token is a pointer to the POOL_SENTINEL 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.

    其表达的要点如下:

    • 线程的 autorelease pool 的实质是指针的堆栈,autorelease pool 是与线程关联的;
    • Autorelease pool 中的指针要么指向需要release的对象,要么是POOL_SENTINELPOOL_SENTINEL是 autorelease pool 的边界
    • Autorelease pool 的 token是指向 autorelease pool 自身的POOL_SENTINEL的指针。当autorelease pool释放时,会释放所有比 tokenhotter更“热”的对象
    • Autorelease pool 中,指针的堆栈被划分到 分页中,分页使用双向链表的数据结构关联,分页可按需添加或删除;
    • 线程本地存储指向hot page“热”分页,“热”分页保存最新的 autorelease 的对象。

    接下来第二章,会从 Runtime 源代码中,找到以上要点的实现原理。

    二、源码分析

    首先以每个 iOS app 工程都会包含main.m文件作为突破口,其源码如下:

    #import <UIKit/UIKit.h>
    #import "AppDelegate.h"
    
    int main(int argc, char * argv[])
    {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    逻辑很简单:1、新建 autorelease pool;2、调用UIApplicationMain(...)函数新建一个UIApplication实例并将代理设置为AppDelegate,开始运行。这是能找到的关于autorelease 的最简短的代码。

    2.1 @autoreleasepool块原理

    打开命令行cdmain.m文件所在目录,使用clang -rewrite-objc main.mmain.m转化为 C/C++ 语言。这里可能会抛如下的错误。

    执行clang命令可能会出错误

    没关系,这里只关心@autoreleasepool的实现,可以把不关心的代码悉数删掉,只留下:

    int main(int argc, char * argv[])
    {
        @autoreleasepool {
            
        }
    }
    

    再重新执行clang命令,成功后会在main.m所在目录下生成一个main.cpp文件,摘出 autorelease pool 相关代码:

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

    其逻辑:

    • 在C语言的作用域{ }内声明一个__AtAutoreleasePool结构体__autoreleasepool会自动触发__AtAutoreleasePool()构建方法;
    • 超出作用域会释放变量占用的内存空间,即自动触发~__AtAutoreleasePool()方法。

    最后,进一步剔除结构体的代码,@autoreleasepool{ }块实际等价于:

    {
        void* token = objc_autoreleasePoolPush();
    
        objc_autoreleasePoolPop(token);
    }
    

    此处的token实际上就是第一章第3个要点所提到的 token。

    再看objc_autoreleasePoolPush()函数以及objc_autoreleasePoolPop(...)函数的源代码:

    void * objc_autoreleasePoolPush(void)
    {
        if (UseGC) return nil;
        return AutoreleasePoolPage::push();
    }
    
    void objc_autoreleasePoolPop(void *ctxt)
    {
        if (UseGC) return;
        AutoreleasePoolPage::pop(ctxt);
    }
    

    忽略UseGC判断逻辑,两者只是分别简单调用了AutoreleasePoolPagepush()pop(...)静态方法。至此,定位到实现 autorelease pool 的关键数据结构AutoreleasePoolPage

    2.2 AutoreleasePoolPage数据结构

    AutoreleasePoolPage的实质是 双向链表节点

    AutoreleasePoolPage的类图如下(忽略用于校验的magichiwat成员变量)。AutoreleasePoolPageparent成员指向当前节点的上一个节点,若parentnull则表示该节点为双向链表的开始节点;child成员指向当前节点的下一个节点;depth成员表示当前节点的深度,满足depth = parent->depth + 1,可以视为节点在双向链表中的索引;next成员指向AutoreleasePoolPage中下一个可分配的地址。

    AutoreleasePoolPage类图.jpg

    从类图的成员变量中,似乎找不到用于存储 autorelease object 的成员变量。那AutoreleasePoolPage是如何保存autorelease object的呢?AutoreleasePoolPage重载了new运算符,指定构建AutoreleasePoolPage实例分配定长的4096字节内存空间。

    /** 来自系统架构头文件 */
    #define I386_PGBYTES            4096
    #define PAGE_SIZE               I386_PGBYTES
    #define PAGE_MAX_SIZE           PAGE_SIZE
    
    /** 来自AutoreleasePoolPage */
    static size_t const SIZE = PAGE_MAX_SIZE; 
    static void * operator new(size_t size) {
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE); 
    }
    

    为什么构建只占用56个字节的AutoreleasePoolPage实例却分配了4096字节的空间呢?因为除开保存AutoreleasePoolPage实例的sizeof(this)长度的空间,其余空间(其中包括用于保存POOL_SENTINEL的8个字节)均用于 以堆栈后入先出的方式 保存 autorelease object。

    不妨将这段空间称为AutoreleasePoolPage实例的堆栈空间。以下是一个堆栈空间为空的AutoreleasePoolPage占用内存的示例:

    AutoreleasePoolPage的实例空间和堆栈空间.jpg

    接下来的章节开始介绍AutoreleasePoolPage是如何操作其堆栈空间的。

    2.2.1 AutoreleasePoolPage的堆栈空间

    AutoreleasePoolPage定义了以下实例方法 查询其堆栈空间的属性及状态。

    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();
    }
    
    bool lessThanHalfFull() {
        return (next - begin() < (end() - begin()) / 2);
    }
    
    • begin()返回指向堆栈空间的初始地址的指针;
    • end()返回指向堆栈空间的结束地址;
    • empty()返回堆栈空间是否为空;
    • full()返回堆栈空间是否已满;
    • lessThanHalfFull()返回堆栈空间是否分配过半。

    AutoreleasePoolPage的关键指针的指向如下图所示:

    AutoreleasePoolPage示例.jpg

    2.2.2 AutoreleasePoolPage基本操作(Private实例方法)

    正式分析本节代码之前,需要先弄清楚 hotPage 、coldPage 的概念。hotPage 保存 autorelease pool 当前所分配到的AutoreleasePoolPage;相应地, coldPage 是从 hotPage 开始沿parent指针链回溯找到的第一个分配的AutoreleasePoolPage

    源代码如下:

    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    
    static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key);
        return result;
    }
    
    static inline void setHotPage(AutoreleasePoolPage *page) 
    {
        tls_set_direct(key, (void *)page);
    }
    
    static inline AutoreleasePoolPage *coldPage() 
    {
        AutoreleasePoolPage *result = hotPage();
        if (result) {
            while (result->parent) {
                result = result->parent;
            }
        }
        return result;
    }
    
    static inline void *tls_get_direct(tls_key_t k) 
    { 
        assert(is_valid_direct_key(k));
    
        if (_pthread_has_direct_tsd()) {
            return _pthread_getspecific_direct(k);
        } else {
            return pthread_getspecific(k);
        }
    }
    static inline void tls_set_direct(tls_key_t k, void *value) 
    { 
        assert(is_valid_direct_key(k));
    
        if (_pthread_has_direct_tsd()) {
            _pthread_setspecific_direct(k, value);
        } else {
            pthread_setspecific(k, value);
        }
    }
    

    其中,tls_get_direct()tls_set_direct()函数分别通过pthread_getspecific()pthread_setspecific()函数,使用Key-Value方式访问线程的私有空间。显然 hotPage 是与线程关联的,在不同的线程上调用AutoreleasePoolPage类的hotPage()静态方法返回的是不同的AutoreleasePoolPage实例。代码中的keyAutoreleasePoolPage的一个const静态变量。

    2.2.2.1 添加对象

    id *add(id obj)实例方法用于向当前AutoreleasePoolPage节点的堆栈空间添加 autorelease object。具体过程是:

    • obj写入next指向的内存地址;
    • next递增(由于next指针占8个字节,因此next递增实质是所指向的地址增加8);
    id *add(id obj)
    {
        assert(!full());
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        return ret;
    }
    

    注意:assert(!full())断言仅在堆栈空间未满的情况下才能调用add(...)

    2.2.2.2移除对象

    void releaseUntil(id *stop)实例方法用于释放AutoreleasePoolPage中,比*stop更晚推入堆栈空间的所有 autorelease object。步骤如下:

    • this->next到达stop之前,进行以下迭代;
    • 找到 hotPage,若 hotPage为空,则沿parent指针一直回溯找到第一个非空的AutoreleasePoolPage,并将其设为 hotPage;
    • next指针递减,obj指向next的内容,重置page->next内存地址中的内容为0xA3A3A3A3
    • obj != POOL_SENTINEL,则调用objc_release(obj)释放obj
    • 完成以上迭代后,将当前AutoreleasePoolPage设置为 hotPage。
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    void releaseUntil(id *stop) 
    {
        while (this->next != stop) {
            AutoreleasePoolPage *page = hotPage();
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }
    
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
    
            if (obj != POOL_SENTINEL) {
                objc_release(obj);
            }
        }
    
        setHotPage(this);
    
    #if DEBUG
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            assert(page->empty());
        }
    #endif
    }
    

    注意:#if DEBUG块中表示,调用releaseUtil()后,stop指针所在的AutoreleasePoolPagechild链上的所有节点都应该为空。

    void releaseAll()删除堆栈空间中的所有对象。

    void releaseAll() 
    {
        releaseUntil(begin());
    }
    

    void kill()从双向链表中移除当前AutoreleasePoolPage节点后的所有节点,包括节点本身。注意kill()不包含释放对象的操作,只是简单移除节点。

    void kill() 
    {
        AutoreleasePoolPage *page = this;
        while (page->child) page = page->child;
    
        AutoreleasePoolPage *deathptr;
        do {
            deathptr = page;
            page = page->parent;
            if (page) {
                page->child = nil;
            }
            delete deathptr;
        } while (deathptr != this);
    }
    

    2.2.3 AutoreleasePoolPage基本操作(Private类方法)

    id *autoreleaseNewPage(id obj)方法仅在DebugPoolAllocation调试配置项打开时才会使用,忽略。

    2.2.3.1 向填满的 page 添加对象

    autoreleaseFullPage(id obj, AutoreleasePoolPage *page)向已填满的page添加对象obj。其逻辑:

    • 顺着pagechild指针链循环,找到第一个未填满的AutoreleasePoolPage节点赋值给page;若最后一个AutoreleasePoolPage也是满的,则新建一个AutoreleasePoolPage赋值给page,接到双向链表末尾。退出循环;
    • page其设置为hotPage,将obj推入page的堆栈空间。
    static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *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);
    }
    /** 新建以传入参数newParent为父节点的AutoreleasePoolPage节点 */
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) 
        : magic(), next(begin()), thread(pthread_self()),
          parent(newParent), child(nil), 
          depth(parent ? 1+parent->depth : 0), 
          hiwat(parent ? parent->hiwat : 0)
    { 
        if (parent) {
            parent->check();
            assert(!parent->child);
            parent->unprotect();
            parent->child = this;
            parent->protect();
        }
        protect();
    }
    
    2.2.3.2 向空 autorelease pool 添加对象

    autoreleaseNoPage(id obj)向空 autorelease pool 添加autorelease object,空AutoreleasePool的判断标准是 hotPage 为nil。其逻辑:

    • 创建一个parentnilAutoreleasePoolPage实例page,并设置为hotPage;
    • obj != POOL_SENTINEL,则添加POOL_SENTINELpage
    • 添加objpage
    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        assert(!hotPage());
    
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
    
        if (obj != POOL_SENTINEL) {
            page->add(POOL_SENTINEL);
        }
    
        return page->add(obj);
    }
    

    至此可对POOL_SENTINEL有第一步认识:AutoreleasePoolPage节点组成的 双向链表的 开始节点 的堆栈空间 的首地址中,必然保存POOL_SENTINEL

    2.2.3.3 向 autorelease pool 添加对象的通用方法

    static inline id *autoreleaseFast(id obj)方法用于将obj添加到autorelease pool,即找到合适的AutoreleasePoolPage(实际上就是 hotPage)并调用其add()方法将obj添加到该分页。其逻辑:

    • 若 hotPage 存在且未填满,则将obj直接添加到 hotPage;
    • 若 hotPage 存在且已填满,则调用autoreleaseFullPage(obj, page)
    • 若 hotPage 不存在,则调用autoreleaseNoPage(obj)
    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);
        }
    }
    

    2.2.4 AutoreleasePoolPage基本操作(Public类方法)

    该节介绍 autorelease pool 暴露给外部的实现 autorelease object 管理的公有类方法。

    2.2.4.1 新建 autorelease pool

    调用AutoreleasePoolPage::push()新建 autorelease pool。其实现只是简单调用了dest = autoreleaseFast(POOL_SENTINEL)并返回destdest实际指向 autorelease pool 的首个AutoreleasePoolPage节点的 堆栈空间的 起始地址,必定是个POOL_SENTINEL

    至此可对POOL_SENTINEL有第二步认识:autorelease pool 的堆栈空间必然以POOL_SENTINEL为起始

    static inline void *push() 
    {
        id *dest;
        dest = autoreleaseFast(POOL_SENTINEL);
    
        assert(*dest == POOL_SENTINEL);
        return dest;
    }
    
    2.2.4.2 释放 autorelease pool

    调用AutoreleasePoolPage::pop(void *token)释放 autorelease pool。

    首先需要了解pageForPointer(const void *token)方法。该方法是用来获取token指针所在分页,其实现是offset = token % SIZE获取token的偏移量,然后result = (AutoreleasePoolPage *)(token - offset)获得所在分页的地址。为什么可以这么计算呢?2.2节中提到的AutoreleasePoolPage类的new运算符重载是关键:malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE)指定了分配内存时按SIZE对齐,也就是说AutoreleasePoolPage实例的内存首地址一定是4096的整数倍

    实现代码稍微较长,删除不关心的逻辑得到以下代码。

    static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;
    
        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_SENTINEL) {
            _objc_fatal("invalid or prematurely-freed autorelease pool %p; ", 
                        token);
        }
    
        page->releaseUntil(stop);
    
        if (page->child) {
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
    
    static AutoreleasePoolPage *pageForPointer(const void *p) 
    {
        return pageForPointer((uintptr_t)p);
    }
    
    static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
    {
        AutoreleasePoolPage *result;
        uintptr_t offset = p % SIZE;
    
        assert(offset >= sizeof(AutoreleasePoolPage));
    
        result = (AutoreleasePoolPage *)(p - offset);
        return result;
    }
    

    pop(void *token)方法的处理逻辑如下:

    • token记为stop,stop命名更能表达传入参数的在方法内部的角色,表示 autorelease pool 释放对象到stop终止;
    • 找到stop所在的AutoreleasePoolPage赋值给page
    • 限定stop必须为POOL_SENTINEL,否则抛出异常;
    • page->releaseUntil(stop)从 autorelease pool 的堆栈空间弹出对象,直到stop地址为止;
    • page->child为非空则需要调用kill()方法清理 autorelease pool 中不必要的空节点:判断若page填满未过半,则删掉pagechild链上的所有节点;若page填满过半且page->child->child为非空,则保留page->child节点而删掉page->childchild链上的所有节点。

    采用上述最后一点的处理策略是为了清理掉无用的AutoreleasePoolPage占用空间的同时,又保留一定的缓冲空间,以避免刚释放完AutoreleasePoolPage又不得不马上新建的情况。

    2.2.4.3 添加对象到 autorelease pool

    使用id autorelease(id obj)类方法添加 autorelease object 到 autorelease pool,只是简单调用了autoreleaseFast(obj)私有类方法。

    static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  *dest == obj);
        return obj;
    }
    

    AutoreleasePoolPage暴露的三个主要接口可以看出,autorelease pool 对 autorelease object 的操作,遵循 逐个添加、批量释放的原则

    2.2.5 Autorelease Pool 与线程

    前文提及 hotPage 和 codePage 的实现,线程中私有空间中保存了 hotPage 的地址,因此在不同的线程上调用AutoreleasePoolPage类的hotPage()静态方法时,返回的是不同的AutoreleasePoolPage实例。AutoreleasePoolPage双向链表中的所有节点的堆栈空间,实际是统一的整体,它是一条线程上创建的所有 autorelease pool 的堆栈。

    线程与AutoreleasePoolPage的关系如下图所示。假设App使用了三条线程主线程Thread_Main、后台线程Thread_A及Thread_B,其中红色箭头表示线程与AutoreleasePoolPage之间的关联,线程通过私有空间中的 Key-Value 映射可以获取到该线程的 hotPage,AutoreleasePoolPage 通过thread指针可获取其关联线程;蓝色箭头表示AutoreleasePoolPage之间使用双向链表通过parentchild指针关联;

    autorelease pool 与线程.jpg

    Autorelease pool 与 RunLoop 也有非常紧密的关系。App 启动后再主线程 RunLoop 会注册两个 Observer,第一个 Observer 监听 Entry 事件,其回调会调用objc_autoreleasePoolPush()函数创建自动释放池;第二个Observer监听两个事件,监听到BeforeWaiting(即将进入休眠)时调用objc_autoreleasePoolPop()函数释放旧的 autorelease pool 并调用objc_autoreleasePoolPush()函数建立新的 autorelease pool ;监听到 Exit 事件时,调用objc_autoreleasePoolPop(void *ctxt)函数释放 autorelease pool (顺便一提:调用pop传入的ctxt参数实际上是调用push新建 autorelease pool 时返回的POOL_SENTINEL的地址)。

    2.2.6 理解 POOL_SENTINEL

    Autorelease pool 的本质是AutoreleasePoolPage双向链表中的某段堆栈空间。双向链表上的 autorelease pool 之间通过POOL_SENTINEL分隔POOL_SENTINEL是全面理解 autorelease pool 实现的关键。

    POOL_SENTINEL的定义其实非常简单:

    #define POOL_SENTINEL nil
    

    首先autorelease(id obj)方法中assert(obj)断言限定 添加到 autorelease pool 中的对象不能为空,但这只是限定了对象添加到 autorelease pool 当时不能为空;其次在添加后,堆栈空间中的对象引用也不可能变为空,因为堆栈空间中已分配的存储单元(8个字节空间)存储的是指向对象的指针,实质为对象的内存地址,无论对象是否已释放,在 autorelease pool 释放之前,该指针的值始终会是该内存地址。因此,autorelease pool 的已分配堆栈空间中,除了POOL_SENTINEL外不可能存在其他nil指针

    如果使用weak指针呢?如以下代码,释放objweakRef指针自动置nil后,autorelease pool 堆栈空间中对应的指针是否也被置nil呢?

    id obj = [[NSObject alloc] init]; //引用计数:1
    __weak id weakRef = obj;
    
    [weakRef autorelease]; //引用计数:1;weakRef:NSObject
    [obj release];  // 引用计数:0;weakRef:nil
    obj = nil;
    

    用两张图表示上述5句代码执行过程中,内存中到底发生了什么:

    第1、2、3句代码.jpg 第4、5句代码.jpg
    • 第1句:见图一绿色文字,在内存栈中分配8个字节保存obj指针,在内存堆中分配连续空间保存实例化的NSObject的实例,假设实例地址为0x60ACC008800,置obj指向0x60ACC008800
    • 第2句:见图一蓝色文字,在内存栈中分配8个字节保存weakRef指针(忽略weak指针实现、引用计数实现等机制 使用到的其他内存),置weakRef指向0x60ACC008800
    • 第3句:见图一红色文字,将obj指针指向的对象的引用添加到 autorelease pool,实质上是将对象的内存地址0x60ACC008800推入 autorelease pool 堆栈空间的栈顶;
    • 第4、5句:见图二黄色文字,执行完成后,内存堆中保存对象的内存被释放,内存栈中的weakRef弱指针被自动置nilobj被代码手动置nil,其值均变为0x0,然而 autorelease pool 堆栈空间中原本指向对象的指针则成为 野指针

    因此,上述代码不仅不能达到目的,且运行以上代码程序会崩溃。原因是:对象release操作后,obj对象被释放,堆栈空间中指向obj的指针就变成了野指针,autorelease pool 释放对象调用指针指向对象的release方法时必然抛EXC_BAD_ACCESS错误。

    三、总结

    总结本文要点如下:

    • Autorelease 是将内存堆中的对象统一交由 autorelease pool 管理的一种内存管理方式。NSObject对象调用autorelease方法时,对象被添加到 autorelease pool 中(内部逻辑不调用retain方法因此refCount不会递增)。在合适的时候,如@autoreleasepool块结尾、或者调用了NSAutoreleasePooldrain方法、或者 autorelease pool 所在线程的 Runloop 的 Observer 观察到 Runloop 的 BeforeWaiting 或 Exit 通知等,系统将调用objc_autoreleasePoolPop()函数释放其中所有的autorelease object(内部逻辑有调用objc_release函数因此refCount会递减);

    • Autorelease体现在 Cocoa框架的工厂方法使用中(创建对象会将其自动添加到当前线程的默认 autorelease pool 中)是一种内存的 延迟释放机制,如[UIImage imageNamed:@"xxx"],在短时间内需频繁创建占用内存较大的对象的场景中,需要慎用这些工厂方法;体现在@autoreleasepool块的使用中,则是一种内存的 提前释放机制,对上述场景则可使用@autoreleasepool块提前释放内存;

    • Autorelease pool 的本质是AutoreleasePoolPage双向链表中的某段堆栈空间。双向链表上的 autorelease pool 之间通过POOL_SENTINEL分隔

    • 线程的私有空间中保存了AutoreleasePoolPage双向链表的 hotPage 的地址。一条AutoreleasePoolPage双向链表与一条线程关联;

    • 新建 autorelease pool 需要记录返回POOL_SENTINEL的地址;释放 autorelease pool 时,以该地址为token释放其对应的 autorelease pool 中的所有对象;

    • NSObject对象调用autorelease方法时,将对象的内存地址推入 autorelease pool 的堆栈空间,autorelease pool 的已分配堆栈空间中,除了POOL_SENTINEL外不可能存在其他nil指针

    参考文章:
    [1] 内存管理总结-autoreleasePool
    [2] 深入理解RunLoop
    [3] opensource-apple/objc4

    相关文章

      网友评论

        本文标题:从Runtime源代码解读内存管理机制——Autorelease

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