iOS - 自动释放池与@autoreleasepool

一、官网关于自动释放池的说明截取

NSAutoreleasePool

  • NSAutoreleasePool 类被用来支持自动引用计数内存管理系统。一个自动释放池存储的对象当自己被销毁的时会向其中的对象发送 release 消息。

Overview

1.png
  • 在一个自动引用计数的环境中(并不是垃圾回收机制),一个包含了多个对象的 NSAutoreleasePool 对象能够接收 autorelease 消息并且当销毁它的时候会对每一个池子中的对象发送 release 消息。因此,发送 autorelease 而不是 release 消息延长了对象的生命周期直到 pool 被清空的时候(当对象被保留的时候会更久)。一个对象能够被放到同一个池子中许多次,在这种情况下每放一次都会收到一个 release 消息。

  • 在引用计数的环境中,Cocoa 期望有一个自动释放池能够保持有效。如果一个池子没有用了,需要自动释放的对象没有被释放从而会造成内存泄漏。在这种情况下,你的程序将会报错。

  • Application Kit 在事件循环开始的时候在主线程创建了一个自动释放池,并且在结束的时候去清空它,从而释放所有进程事件中生成的自动释放的对象。如果使用了 Application Kit ,就没必要再去创建自己的自动释放池。然而,如果你的应用在事件循环中创建了很多临时的自动释放的对象,创建临时的自动释放池会将有助于削减你内存峰值的占用。

  • 你创建了一个 NSAutoreleasePool 对象根据 alloc 和 init 消息并且用 drain 来清空它。因为你不能够保留一个自动释放池(或者自动释放它。), 清空一个池子最终会影响它的销毁。你应该在创建它的同一个上下文来进行销毁的工作。

  • 每一个线程(包括主线程)包含一个它自己的自动释放池对象的堆栈。作为一个新的被创建的池子,它们被添加到堆栈的顶部。当池子被释放的时候,它们从栈中被移除。自动释放的对象被放在当前线程的自动释放池的顶部。当一个线程终止的时候,它自动清空与它关联的所有的自动释放池。

线程

  • 如果你想要 Cocoa 在 ApplicationKit 的主线程之外调用,比如你创建了一个 Foundation 的 应用或者你创建了一个线程,你需要创建你自己的自动释放池。
  • 如果应用或线程是长久保存的并且潜在的生成了很多自动释放的对象,这时应该定期的清空并且创建自动释放池(就像 Application Kit 在主线程中做的那样);否则,对象的积累会增加内存的占用。如果,独立的线程并没有使用 Cocoa 的调用,你没有必要去创建一个自动释放池。

注意
如果使用了 POSIX 线程 APIS 而不是 NSThread 对象来创建线程,你不能使用 Cocoa,包括 NSautoreleasePool,除非 Cocoa 是在多线程模式下,Cocoa 进入了多线程模式只有在首次创建 NSThread 对象的时候,为了在第二个 POSIX 线程中使用 Cocoa ,你的应用必须首先至少创建了一个独立的 NSThread 对象,这个对象可以立即退出。你可以通过 NSThread 类方法 isMultiTheraded 来测试 Cocoa 是否在多线程模式下。

垃圾回收

  • 在垃圾回收的环境下,是不需要自动释放池的。你可能写了一个 framework ,它被设计用来在垃圾回收环境和引用计数环境下都可工作。在这种情况下,你可以使用自动释放池去提示回收器回收可能是合适的。在垃圾回收环境中,如果必要会发送一个 drain 消息到池子中去触发垃圾回收机制;然而,release,是一个空操作。在引用计数的环境中,drain 和 release 的效果是一样的。通常,你应该使用 drain 而不是 release。

二、什么时候是用 @autoreleasepool

  • 写基于命令行的的程序时,就是没有UI框架,如 AppKit 等 Cocoa 框架时。
  • 当我们的应用有需要创建大量的临时变量的时候,可以是用 @autoreleasepool 来减少内存峰值。
  • 为什么?
    自动释放池可以延长对象的声明周期,如果一个事件周期很长,比如有一个很长的循环逻辑,那么一个临时变量可能很长时间都不会被释放,一直在内存中保留,那么内存的峰值就会一直增加,但是其实这个临时变量是我们不再需要的。这个时候就通过创建新的自动释放池来缩短临时变量的生命周期来降低内存的峰值。
  • 这是一个说明这个问题的很好的例子。
  • YYKit 中的使用
for (int i = 0; i < count; i++) {
@autoreleasepool {
    id imageSrc = _images[i];
    NSDictionary *frameProperty = NULL;
    if (_type == YYImageTypeGIF && count > 1) {
    frameProperty = @{(NSString *)kCGImagePropertyGIFDictionary : @{(NSString *)     kCGImagePropertyGIFDelayTime:_durations[i]}};
    } else {
      frameProperty = @{(id)kCGImageDestinationLossyCompressionQuality :       @(_quality)};
    }
    if ([imageSrc isKindOfClass:[UIImage class]]) {
    UIImage *image = imageSrc;
    if (image.imageOrientation != UIImageOrientationUp && image.CGImage) {
    CGBitmapInfo info = CGImageGetBitmapInfo(image.CGImage) |         CGImageGetAlphaInfo(image.CGImage);
    CGImageRef rotated = YYCGImageCreateCopyWithOrientation(image.CGImage,     image.imageOrientation, info);
    if (rotated) {
      image = [UIImage imageWithCGImage:rotated];
      CFRelease(rotated);
    }
  }
    if (image.CGImage) CGImageDestinationAddImage(destination, ((UIImage     *)imageSrc).CGImage, (CFDictionaryRef)frameProperty);
    } else if ([imageSrc isKindOfClass:[NSURL class]]) {
      CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)imageSrc, NULL);
    if (source) {
      CGImageDestinationAddImageFromSource(destination, source, i,     (CFDictionaryRef)frameProperty);
      CFRelease(source);
  }
  } else if ([imageSrc isKindOfClass:[NSData class]]) {
        CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageSrc, NULL);
        if (source) {
          CGImageDestinationAddImageFromSource(destination, source, i,       (CFDictionaryRef)frameProperty);
          CFRelease(source);
        }
      }
    }
}

三、release 和 drain的区别

  • 当我们向自动释放池 pool 发送 release 消息,将会向池中临时对象发送一条 release 消息,并且自身也会被销毁。
  • 向它发送drain消息时,将会向池中临时对象发送一条release消息。
  • 官方解释
  • release:
    释放并且出栈接收者。(ARC)
  • drain:
    • 在引用计数环境中,会释放并且出栈接受者。
    • 在垃圾回收环境中,会触发垃圾回收机制如果上次分配的内存集合大于当前的阈值。

四、Autoreleasepool 底层实现

  • 首先我们去可以查看一下,clang 转成 c++ 的 autoreleasepool 的源码:
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

可以发现objc_autoreleasePoolPush() 和 objc_autoreleasePoolPop() 这两个方法。

  • 再看一下runtime 中 Autoreleasepool 的结构,通过阅读源码可以看出 Autoreleasepool 是一个由 AutoreleasepoolPage 双向链表的结构,其中 child 指向它的子 page,parent 指向它的父 page。


    双向链表结构图
  • 并且每个 AutoreleasepoolPage 对象的大小都是 4096 个字节。
#define PAGE_MAX_SIZE           PAGE_SIZE
#define PAGE_SIZE       I386_PGBYTES
#define I386_PGBYTES        4096        /* bytes per 80386 page */
  • AutoreleasepoolPage 通过压栈的方式来存储每个需要自动释放的对象。
//入栈方法
    static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            //在 Debug 情况下每一个自动释放池 都以一个新的 poolPage 开始
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
//正常情况下,调用 push 方法会先插入一个 POOL_BOUNDARY 标志位
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
  • 然后我们来看看 runtime 中的源码
/***********************************************************************
 自动释放池的实现:
 一个线程的自动释放池是一个指针堆栈
 每一个指针或者指向被释放的对象,或者是自动释放池的 POOL_BOUNDARY,POOL_BOUNDARY 是自动释放池的边界。
 一个池子的 token 是指向池子 POOL_BOUNDARY 的指针。当池子被出栈的时候,每一个高于标准的对象都会被释放掉。
 堆栈被分成一个页面的双向链表。页面按照需要添加或者删除。
 本地线程存放着指向当前页的指针,在这里存放着新创建的自动释放的对象。
**********************************************************************/
// Set this to 1 to mprotect() autorelease pool contents
//将这个设为 1 可以通过mprotect修改映射存储区的权限来更改自动释放池的内容
#define PROTECT_AUTORELEASEPOOL 0
#define CHECK_AUTORELEASEPOOL (DEBUG)
BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj));
BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token));
namespace {
//对AutoreleasePoolPage进行完整性校验
struct magic_t {
    static const uint32_t M0 = 0xA1A1A1A1;
#   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;
    uint32_t m[4];    
    magic_t() {
        assert(M1_len == strlen(M1));
        assert(M1_len == 3 * sizeof(m[1]));
        m[0] = M0;
        strncpy((char *)&m[1], M1, M1_len);
    }
    ~magic_t() {
        m[0] = m[1] = m[2] = m[3] = 0;
    }
    bool check() const {
        return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
    }
    bool fastcheck() const {
#if CHECK_AUTORELEASEPOOL
        return check();
#else
        return (m[0] == M0);
#endif
    }
#   undef M1
};
//自动释放页
class AutoreleasePoolPage 
{
  //EMPTY_POOL_PLACEHOLDER 被存放在本地线程存储中当一个池入栈并且没有存放任何对象的时候。这样在栈顶入栈出栈并且没有使用它们的时候会节省内存。
#   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;
//页中对象的下一位索引
    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);
    }
//设置当前内存可读
    inline void protect() {
#if PROTECT_AUTORELEASEPOOL
        mprotect(this, SIZE, PROT_READ);
        check();
#endif
    }
//设置当前内存可读可写
    inline void unprotect() {
#if PROTECT_AUTORELEASEPOOL
        check();
        mprotect(this, SIZE, PROT_READ | PROT_WRITE);
#endif
    }
  //初始化
    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();
    }
//析构
    ~AutoreleasePoolPage() 
    {
        check();
        unprotect();
        assert(empty());
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        assert(!child);
    }
//被破坏的
    void busted(bool die = true) 
    {
        magic_t right;
        (die ? _objc_fatal : _objc_inform)
            ("autorelease pool page %p corrupted\n"
             "  magic     0x%08x 0x%08x 0x%08x 0x%08x\n"
             "  should be 0x%08x 0x%08x 0x%08x 0x%08x\n"
             "  pthread   %p\n"
             "  should be %p\n", 
             this, 
             magic.m[0], magic.m[1], magic.m[2], magic.m[3], 
             right.m[0], right.m[1], right.m[2], right.m[3], 
             this->thread, pthread_self());
    }
//校验
    void check(bool die = true) 
    {
        if (!magic.check() || !pthread_equal(thread, pthread_self())) {
            busted(die);
        }
    }
//快速校验
    void fastcheck(bool die = true) 
    {
#if CHECK_AUTORELEASEPOOL
        check(die);
#else
        if (! magic.fastcheck()) {
            busted(die);
        }
#endif
    }
//页的开始位置
    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);
    }
//添加对象
    id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }
//释放所有对象
    void releaseAll() 
    {
        releaseUntil(begin());
    }
//释放到 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
            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;
//将页索引内容置为 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;
        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);
    }
//释放本地线程存储空间
    static void tls_dealloc(void *p) 
    {
        if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
            // No objects or pool pages to clean up here.
            return;
        }
        // reinstate TLS value while we work
        setHotPage((AutoreleasePoolPage *)p);
        if (AutoreleasePoolPage *page = coldPage()) {
            if (!page->empty()) pop(page->begin());  // pop all of the pools
            if (DebugMissingPools || DebugPoolAllocation) {
                // pop() killed the pages already
            } else {
                page->kill();  // free all of the pages
            }
        }
        // clear TLS value so TLS destruction doesn't loop
        setHotPage(nil);
    }
//获取 AutoreleasePoolPage
    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);
        result->fastcheck();
        return result;
    }
//是否有空池占位符
    static inline bool haveEmptyPoolPlaceholder()
    {
        id *tls = (id *)tls_get_direct(key);
        return (tls == EMPTY_POOL_PLACEHOLDER);
    }
//设置空池占位符
    static inline id* setEmptyPoolPlaceholder()
    {
        assert(tls_get_direct(key) == nil);
        tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
        return EMPTY_POOL_PLACEHOLDER;
    }
//获取当前页
    static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }
//设置当前页
    static inline void setHotPage(AutoreleasePoolPage *page) 
    {
        if (page) page->fastcheck();
        tls_set_direct(key, (void *)page);
    }
//获取 coldPage
    static inline AutoreleasePoolPage *coldPage() 
    {
        AutoreleasePoolPage *result = hotPage();
        if (result) {
            while (result->parent) {
                result = result->parent;
                result->fastcheck();
            }
        }
        return result;
    }
//快速释放
    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", 
                         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);
    }
    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(obj);
    }
//公开方法
public:
//自动释放
    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;
    }
//入栈
    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;
    }
//兼容老的 SDK 出栈方法
    static void badPop(void *token)
    {
        // Error. For bincompat purposes this is not 
        // fatal in executables built with old SDKs.
        if (DebugPoolAllocation  ||  sdkIsAtLeast(10_12, 10_0, 10_0, 3_0)) {
            // OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal.
            _objc_fatal
                ("Invalid or prematurely-freed autorelease pool %p.", token);
        }
        // Old SDK. Bad pop is warned once.
        static bool complained = false;
        if (!complained) {
            complained = true;
            _objc_inform_now_and_on_crash
                ("Invalid or prematurely-freed autorelease pool %p. "
                 "Set a breakpoint on objc_autoreleasePoolInvalid to debug. "
                 "Proceeding anyway because the app is old "
                 "(SDK version " SDK_FORMAT "). Memory errors are likely.",
                     token, FORMAT_SDK(sdkVersion()));
        }
        objc_autoreleasePoolInvalid(token);
    }
//出栈
    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);
            }
        }
//打印 hiwat
        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();
            }
        }
    }
    static void init()
    {
        int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, 
                                             AutoreleasePoolPage::tls_dealloc);
        assert(r == 0);
    }
//打印
    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));
            }
        }
    }
//打印所有
    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("##############");
    }
//打印 hiwat 
    static void printHiwat()
    {
        // Check and propagate high water mark
        // Ignore high water marks under 256 to suppress noise.
        AutoreleasePoolPage *p = hotPage();
        uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin());
        if (mark > p->hiwat  &&  mark > 256) {
            for( ; p; p = p->parent) {
                p->unprotect();
                p->hiwat = mark;
                p->protect();
            }
            _objc_inform("POOL HIGHWATER: new high water mark of %u "
                         "pending releases for thread %p:", 
                         mark, pthread_self());
            void *stack[128];
            int count = backtrace(stack, sizeof(stack)/sizeof(stack[0]));
            char **sym = backtrace_symbols(stack, count);
            for (int i = 0; i < count; i++) {
                _objc_inform("POOL HIGHWATER:     %s", sym[i]);
            }
            free(sym);
        }
    }
#undef POOL_BOUNDARY
};

参考文章
官方文档

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

推荐阅读更多精彩内容