iOS 自动释放池

这篇文章会在源代码层面介绍Objective-C中自动释放池,以及方法的autorelease的具体实现

从main函数开始

main 函数可以说是在整个iOS开发中非常不起眼的一个函数,却是整个iOS应用的入口。
main.m 文件中的内容是这样的:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

在这个@autoreleasepool block中只包含了一行代码,这行代码将所有的事件、消息全部交给了UIApplication来处理,但是这不是本文关注的重点。
需要注意的是:整个iOS的应用都是包含在一个自动释放池block中的。

@autoreleasepool

@autoreleasepool到底是什么?我们在命令行中使用clang-rewrite-objc main.m 让编译器重新改写这个文件:

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

这里只看main函数中的代码

在这个文件中,有一个非常奇怪的__AtAutoreleasePool 的结构体,前面的注释写到/* @autoreleasepool */ 。也就是说@autoreleasepool {} 被转换为一个 __AtAutoreleasePool 的结构体。
想要弄清楚这行代码的意义,我们要在main.cpp 中查找名为 __AtAutoreleasePool 的结构体:

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

这个结构体会在初始化时调用objc_autoreleasePoolPush()方法,会在析构时调用objc_autoreleasePoolPop方法。
这表明,我们的main 函数在实际工作时其实是这样的:

int main(int argc, const char * argv[]) {
    {
        void *atautoreleasepoolobj = objc_autoreleasePoolPush();
        
        // do whatever you want
        
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

@autoreleasepool 只是帮助我们少写了这两行代码而已,让代码看起来更美观,然后要根据上述两个方法来分析自动释放池的实现。

Autoreleasepool是什么

这一节开始分析objc_autoreleasePoolPush 和 objc_autoreleasePoolPop 的实现,在 runtime源码的NSObject.mm文件中:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

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

上面的方法看上去是对 AutoreleasePoolPage 对应的静态方法 push 和 pop 的封装。
这一小节会按照下面的顺序逐步解析代码中的内容:

AutoreleasePoolPage的结构

AutoreleasePoolPage 是一个C++中的类,它在 NSObject.mm 中的定义是这样的:

class AutoreleasePoolPage 
{
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}
  • magic 用于对当前 AutoreleasePoolPage 完整性的校验
  • thread 保存了当前页所在的线程
  • parent 指向父节点,第一个节点的parent值为nil
  • child 指向子节点,最后一个节点的child值为nil

每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是4096字节(16进制0x1000)

双向链表

自动释放池中的 AutoreleasePoolPage 是以双向链表的形式连接起来的。

parent 和child 就是用来构造双向链表的指针。

自动释放池中的栈

如果我们的一个 AutoreleasePoolPage 被初始化在内存的 0x100816000 ~ 0x100817000 中, 其中有 56 Byte(字节)用于存储 AutoreleasePoolPage 的成员变量, 剩下的 0x100816038 ~ 0x100817000 都是用来存储加入到自动释放池中的对象。

AutoreleasePoolPage 的内存大小为56 字节, magic_t 结构体成员magic 占用内存为 uint32_t m[4], 即为 4 * 4 共16字节;属性next 、thread、parent、child 均占8个字节,共32字节;uint32_t 两个 depth 和 hiwat 各占4字节,共8字节。

id * begin() {
    return (id *) ((uint8_t *)this+sizeof(*this));
}

id * end() {
    return (id *) ((uint8_t *)this+SIZE);
}
  • begin() 和 end() 这两个类的实例方法帮助我们快速获取 0x100816038 ~ 0x100817000 这一范围的边界地址。

  • next 指向了下一个为空的内存地址,如果 next 指向的地址加入了一个 object,它就会移动到下一个为空的内存地址中。

POOL_BOUNDARY (哨兵对象)

到了这里,你可能想知道 POOL_BOUNDARY 到底是什么,还有它为什么在栈中。
首先回答第一个问题:POOL_BOUNDARY 只是 nil 的别名。

#   define POOL_BOUNDARY nil

在每个自动释放池初始化调用 objc_autoreleasePoolPush 的时候,都会把一个 POOL_BOUNDARY push 到自动释放池的栈顶,并且返回这个 POOL_BOUNDARY 哨兵对象。

int main(int argc, const char * argv[]) {
    {
        void *atautoreleasepoolobj = objc_autoreleasePoolPush();
        
        // do whatever you want
        
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

上面的 atautoreleasepoolobj 就是一个 POOL_BOUNDARY 。

而当方法 objc_autoreleasePoolPop 调用时,就会向自动释放池中的对象发送 release 消息,直到第一个 POOL_BOUNDARY 。

objc_autoreleasePoolPush 方法

我们来重新回顾一下 objc_autoreleasePoolPush 方法:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

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

static inline void *push()
{
    id *dest;
    if (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;
}

根据DebugPoolAllocation 判断进入 autoreleaseNewPage 或者 autoreleaseFast,并传入哨兵对象 POOL_BOUNDARY:

static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
    AutoreleasePoolPage *page = hotPage();
    if (page) return autoreleaseFullPage(obj, page);
    else return autoreleaseNoPage(obj);
}

在这里会进入一个比较关键的方法 autoreleaseFast,并传入哨兵对象 POOL_BOUNDARY:

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

autoreleaseFast 方法分三种情况选择不同的代码执行:

  • 有 hotPage 并且当前 page 不满时
    • 调用 page->add(obj) 方法将对象添加到 AutoreleasePoolPage 的栈中
  • 有 hotPage 并且当前page 已满时
    • 调用 autoreleaseFullPage 初始化一个新的页
    • 调用 page->add(obj) 方法将对象添加到 AutoreleasePoolPage 的栈中
  • 无 hotPage时
    • 调用 autoreleaseNoPage 穿件一个 hotPage
    • 调用 page->add(obj) 方法将对象添加到 AutoreleasePoolPage 的栈中
      最后都会调用 page->add(obj) 将对象添加到自动释放池中。

hotPage 可以理解为当前正在使用的 AutoreleasePoolPage 。

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的 child指针,获取下一个page对象,
    // 如果下一个page对象为空,则调用AutoreleasePoolPage创建一个新的page对象
    // while 循环判断page,直到page 没有装满
    do {
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    // 将while循环得到的page对象设置为hotpage
    setHotPage(page);
    
    // 将obj对象加入到page中
    return page->add(obj);
}

autoreleaseFullPage 方法的代码执行:

  • 1.传入的page必须是hotPage, 必须full。
  • 2.通过child 指针,从当前page寻找,直到获取一个没有装满的page,或者创建一个新的page。
  • 3.将通过while遍历获取到的page设置为hotPage,并且将obj加入到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
    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",
                     pthread_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);
}

autoreleaseNoPage 方法的代码执行:

    1. 创建一个新的page
    1. 将page设置为hotpage
    1. 根据判断是否需要将 POOL_BOUNDARY 哨兵对象加入到page
    1. 将obj加入到page中
id *add(id obj)
{
    assert(!full());
    unprotect();
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;
    protect();
    return ret;
}

page->add 添加对象
这个方法就是一个压栈的操作,将对象加入 AutoreleasePoolPage 然后移动栈顶的指针。
<a name="3"> </a>

objc_autoreleasePoolPop 方法

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

我们一般都会在这个方法中传入一个哨兵对象 POOL_BOUNDARY

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.
            pop(coldPage()->begin());
        } else {
            // Pool was never used. Clear the placeholder.
            setHotPage(nil);
        }
        return;
    }

    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 (PrintPoolHiwat) printHiwat();

    page->releaseUntil(stop);

    // memory: delete empty children
    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);
    }
    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();
        }
    }
}

该pop方法总共做了三件事:

  • 1.使用 pageForPointer 获取 token 所在的 AutoreleasePoolPage
  • 2.调用 releaseUntil 方法释放栈中的对象,直到 stop
  • 3.调用 page 的 kill 方法
    • 当前page不为空时, 如果当前page没有child,就保留当前page,不调用当前page的kill
    • 当前page不为空时, 如果当前page有child
      • 当前page占用空间不超过一半,从child开始释放,也就是调用当前page->child->kill
      • 当前page占用空间超过一半,没有 孙子辈 page时,当前page的 child 也就是不释放了 (为了性能提升,少释放一个page的内存空间);有孙子辈 page时,从孙子辈page 开始释放,也就是调用page->child->child->kill

pageForPointer 获取 AutoreleasePoolPage

static AutoreleasePoolPage *pageForPointer(const void *p)
{
    return pageForPointer((uintptr_t)p);
}

static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
    AutoreleasePoolPage *result;
    
    //SIZE: 4096,每页page的大小
    // p % SIZE : 获取p 在page中的偏移地址
    uintptr_t offset = p % SIZE;

    // 边界检测: 偏移地址 <= page 的内存大小
    assert(offset >= sizeof(AutoreleasePoolPage));

    //获取p所在page的起始地址
    result = (AutoreleasePoolPage *)(p - offset);
    
    result->fastcheck();

    return result;
}

pageForPointer 方法主要是通过内存地址的操作,获取当前指针所在页的首地址

将指针与页面的大小,也就是4096 取模,得到当前指针的偏移量,因为所有的 AutoreleasePoolPage 在内存中都是对齐的。
最后调用方法 fastcheck 来检查当前的 result 是不是一个 AutoreleasePoolPage

releaseUntil 释放对象

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循环,直到next == stop
    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为空,利用parent指针找到一个不为空的page,并设置为hotpage
        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }

        //将page所在的内存区域设置为可读可写
        page->unprotect();
        
        //通过next指针获取page中记录的对象,next -1 前移
        id obj = *--page->next;
        
        // void *memset(void *s, int ch, size_t n);
        // 将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
        
        // 将当前对象的 8个字节 用0xA3 替换
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        
        // 修改完page中的值后,设置page所在的内存区域为只读
        page->protect();

        // 如果对象不是哨兵对象则释放
        if (obj != POOL_BOUNDARY) {
            objc_release(obj);
        }
    }

    // 把当前 page 设置 hotpage
    setHotPage(this);

#if DEBUG
    // we expect any children to be completely empty
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        assert(page->empty());
    }
#endif
}

用一个while 循环持续释放 AutoreleasePoolPage 中的内容,直到 next 指向了 stop 。
使用 memset 将内存的内容设置成 SCRIBBLE (常量0xA3),然后使用 objc_release 释放对象内存。

releaseUntil 函数只是将page中的对象释放了,并且对应的位置用 SCRIBBLE (常量0xA3)填充,但是child/parent 指针没有清空,也就是说page还在内存中,没有释放。释放page的操作在后续kill完成。

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

kill会将当前页面以及子页面全部删除,释放AutoreleasePoolPage占用空间,是从最尾部的子page开始释放

autorelease 方法

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

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

在autorelease 方法的调用栈中,最终都会调用到上面提到的 autoreleaseFast 方法,将当前对象加入到 AutoreleasePoolPage 中。 由于上面已经分析过 autoreleaseFast 方法的实现了,参考上面。

实战验证自动释放池内存结构

在 ARC 模式下,是无法手动调用 autorelease,所以要将项目切换至MRC模式 Build Settings -> Objective-C Automatic Reference Counting 设置为 NO,如下图所示:


  • 需要用到_objc_autoreleasePoolPrint
void 
_objc_autoreleasePoolPrint(void)
{
    AutoreleasePoolPage::printAll();
}

很简单,就是对 AutoreleasePoolPage 类方法 printAll 的调用

static void printAll()
{
    _objc_inform("##############");
    _objc_inform("AUTORELEASE POOLS for thread %p", pthread_self());

    AutoreleasePoolPage *page;
    ptrdiff_t objects = 0;
    for (page = coldPage(); page; page = page->child) {
        objects += page->next - page->begin();
    }
    _objc_inform("%llu releases pending.", (unsigned long long)objects);

    if (haveEmptyPoolPlaceholder()) {
        _objc_inform("[%p]  ................  PAGE (placeholder)",
                     EMPTY_POOL_PLACEHOLDER);
        _objc_inform("[%p]  ################  POOL (placeholder)",
                     EMPTY_POOL_PLACEHOLDER);
    }
    else {
        for (page = coldPage(); page; page = page->child) {
            page->print();
        }
    }

    _objc_inform("##############");
}

存在page的情况下,循环遍历page,调用page的print 方法

void print()
{
    _objc_inform("[%p]  ................  PAGE %s %s %s", this,
                 full() ? "(full)" : "",
                 this == hotPage() ? "(hot)" : "",
                 this == coldPage() ? "(cold)" : "");
    check(false);
    for (id *p = begin(); p < next; p++) {
        if (*p == POOL_BOUNDARY) {
            _objc_inform("[%p]  ################  POOL %p", p, p);
        } else {
            _objc_inform("[%p]  %#16lx  %s",
                         p, (unsigned long)*p, object_getClassName(*p));
        }
    }
}
  • 在main.m 中添加如下代码
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];
            NSLog(@"%p", objc);
        }
        
        //调用
        _objc_autoreleasePoolPrint();
    }
    return 0;
}

运行项目,打印结果如下:

2024-03-09 20:15:22.064402+0800 DMAutoreleasePoolDemo[38628:1055312] 0x105254ab0
2024-03-09 20:15:22.064568+0800 DMAutoreleasePoolDemo[38628:1055312] 0x105254710
2024-03-09 20:15:22.064587+0800 DMAutoreleasePoolDemo[38628:1055312] 0x105242740
2024-03-09 20:15:22.064602+0800 DMAutoreleasePoolDemo[38628:1055312] 0x105240d20
2024-03-09 20:15:22.064616+0800 DMAutoreleasePoolDemo[38628:1055312] 0x105240ca0
objc[38628]: ##############
objc[38628]: AUTORELEASE POOLS for thread 0x10008c580
objc[38628]: 6 releases pending.
objc[38628]: [0x10600a000]  ................  PAGE  (hot) (cold)
objc[38628]: [0x10600a038]  ################  POOL 0x10600a038
objc[38628]: [0x10600a040]       0x105254ab0  NSObject
objc[38628]: [0x10600a048]       0x105254710  NSObject
objc[38628]: [0x10600a050]       0x105242740  NSObject
objc[38628]: [0x10600a058]       0x105240d20  NSObject
objc[38628]: [0x10600a060]       0x105240ca0  NSObject
objc[38628]: ##############
Program ended with exit code: 0

从打印结果我们看到有6个对象,但是我们压栈的对象是5个,另一个其实是前面说到的哨兵对象(边界),目的是为了防止越界。另外,从地址的打印,我们也看到了哨兵对象与首地址相差了0x38(十进制56 位)刚好就是 AutoreleasePoolPage 所占的内存大小。

  • 将上述for循环改为505次,再次运行项目,查看打印结果
objc[38809]: ##############
objc[38809]: AUTORELEASE POOLS for thread 0x10008c580
objc[38809]: 506 releases pending.
objc[38809]: [0x10580b000]  ................  PAGE (full)  (cold)
objc[38809]: [0x10580b038]  ################  POOL 0x10580b038
objc[38809]: [0x10580b040]       0x10070e770  NSObject
objc[38809]: [0x10580b048]       0x101067ca0  NSObject
objc[38809]: [0x10580b050]       0x101068200  NSObject
objc[38809]: [0x10580b058]       0x101060170  NSObject
objc[38809]: [0x10580b060]       0x101060a10  NSObject
objc[38809]: [0x10580b068]       0x101062b80  NSObject
objc[38809]: [0x10580b070]       0x1010606c0  NSObject
objc[38809]: [0x10580b078]       0x10105fee0  NSObject
objc[38809]: [0x10580b080]       0x10071a3d0  NSObject
......................................................
......................................................
......................................................
objc[38809]: [0x10580bfc8]       0x10122b8a0  NSObject
objc[38809]: [0x10580bfd0]       0x10122b8b0  NSObject
objc[38809]: [0x10580bfd8]       0x10122b8c0  NSObject
objc[38809]: [0x10580bfe0]       0x10122b8d0  NSObject
objc[38809]: [0x10580bfe8]       0x10122b8e0  NSObject
objc[38809]: [0x10580bff0]       0x10122b8f0  NSObject
objc[38809]: [0x10580bff8]       0x10071aec0  NSObject
objc[38809]: [0x105814000]  ................  PAGE  (hot) 
objc[38809]: [0x105814038]       0x10071aed0  NSObject
objc[38809]: ##############

从打印结果可以看到,第一页已经存满了,存储了504个需要释放的对象,第二页存储了一个对象。

  • 将上述for循环改为1010次,再次巡行项目,查看打印结果
objc[39028]: ##############
objc[39028]: AUTORELEASE POOLS for thread 0x10008c580
objc[39028]: 1011 releases pending.
objc[39028]: [0x10080c000]  ................  PAGE (full)  (cold)
objc[39028]: [0x10080c038]  ################  POOL 0x10080c038
objc[39028]: [0x10080c040]       0x10100faa0  NSObject
objc[39028]: [0x10080c048]       0x101109390  NSObject
objc[39028]: [0x10080c050]       0x101108c20  NSObject
......................................................
......................................................
......................................................
objc[39028]: [0x10080cfd0]       0x10110a0a0  NSObject
objc[39028]: [0x10080cfd8]       0x101404820  NSObject
objc[39028]: [0x10080cfe0]       0x10110a0b0  NSObject
objc[39028]: [0x10080cfe8]       0x1014047c0  NSObject
objc[39028]: [0x10080cff0]       0x10110a0c0  NSObject
objc[39028]: [0x10080cff8]       0x10102c030  NSObject
objc[39028]: [0x10080f000]  ................  PAGE (full)  
objc[39028]: [0x10080f038]       0x10110a0d0  NSObject
objc[39028]: [0x10080f040]       0x10110a0e0  NSObject
objc[39028]: [0x10080f048]       0x10102c040  NSObject
objc[39028]: [0x10080f050]       0x10110a0f0  NSObject
objc[39028]: [0x10080f058]       0x1014047d0  NSObject
......................................................
......................................................
......................................................
objc[39028]: [0x10080ffd8]       0x101027800  NSObject
objc[39028]: [0x10080ffe0]       0x101405310  NSObject
objc[39028]: [0x10080ffe8]       0x101405320  NSObject
objc[39028]: [0x10080fff0]       0x10110ac50  NSObject
objc[39028]: [0x10080fff8]       0x101027810  NSObject
objc[39028]: [0x100811000]  ................  PAGE  (hot) 
objc[39028]: [0x100811038]       0x101027820  NSObject
objc[39028]: ##############
Program ended with exit code: 0

通过运行发现,第一页存储504个,第二页存储505个,第三页存储1个

自动释放池第一页存放1个哨兵对象加504个需要释放的对象,当第一页压栈满了,就会开辟新的一页,从第二页开始可以存放最多505个对象(一页的大小为505 * 8 = 4040字节)

同样这个结论可以通过 AutoreleasePoolPage 中 SIZE 来验证,定义中 PAGE_MAX_SIZE 大小为 4096字节,再起构造函数中对象的压栈位置 begin() 是从 首地址 +56 字节开始的,所以一个page中实际可以存储 4096 - 56 = 4040 字节,转换成对象 4040 / 8 = 505 个,因为第一页有哨兵对象,最多存储504个


小结

整个自动释放池 AutoreleasePool 的事项以及 autorelease 方法都已经分析完了,我们再来回顾一下文章中的主要内容:

  • 自动释放池是由 AutoreleasePoolPage 以双向链表的方式实现的
  • 当对象调用 autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中
  • 调用 AutoreleasePoolPage::pop 方法会向栈中的对象发送 release 消息
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容