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閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容