Skip to content

自动释放池

jiaxw32 edited this page Feb 8, 2021 · 12 revisions

@autoreleasepool 语法糖

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        printf("Hello, World!\n");
    }
    return 0;
}

使用 clang -rewrite-objc main.m 让编译器重写后结果如下:

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

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        printf("Hello, World!\n");
    }
    return 0;
}

@autoreleasepool{} 作用实际相当于

{
    void * atautoreleasepoolobj = objc_autoreleasePoolPush();
    // other code...
    objc_autoreleasePoolPop(atautoreleasepoolobj);
}

objc_autoreleasePoolPushobjc_autoreleasePoolPop 分别调用了 AutoreleasePoolPage 的静态方法 pushpop

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

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

AutoreleasePoolPage 是什么?

  1. 一个线程的自动释放池是一个堆栈,用来存放待释放的对象的指针。 A thread's autorelease pool is a stack of pointers.

  2. 每个指针要么是待释放的对象,要么是POOL_BOUNDARY,它是一个自动释放的池边界。 Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.

  3. 池令牌是指向该池的POOL_BOUNDARY的指针。当池被弹出时,每个比哨兵热的对象都会被释放。 A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.

  4. 自动释放池堆栈是用双向链表链接的一个个页面,页面根据需要增加和删除。 The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.

  5. 线程本地存储指向热页面,该页面用于存储最新待自动释放的对象。 Thread-local storage points to the hot page, where newly autoreleased objects are stored.

AutoreleasePoolPage 类的实现

#define PAGE_MIN_SIZE           (1 << 12)

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_data_t;  //这里使用了友元结构体,不甚也解该语法,暂时略过,有时间再看!

public:
    static size_t const SIZE = PAGE_MIN_SIZE; //size and alignment, power of 2
    
private:
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const COUNT = SIZE / sizeof(id);
}

AutoreleasePoolPage 看着与线程有关,暂时不关注,以后再研究。AutoreleasePoolPage 的大小为 1 << 12,正好 4KB,即 iOS 中最小页的大小!

In OS X and in earlier versions of iOS, the size of a page is 4 kilobytes. In later versions of iOS, A7- and A8-based systems expose 16-kilobyte pages to the 64-bit userspace backed by 4-kilobyte physical pages, while A9 systems expose 16-kilobyte pages backed by 16-kilobyte physical pages!

iPhone7 使用 frida 测试页大小为 16KB

[iPhone::App Store]-> console.log(Process.pageSize) //输出结果:16384

AutoreleasePoolPage 继承自 AutoreleasePoolPageData,实现如下:

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    magic_t const magic;
    __unsafe_unretained id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
    
    //AutoreleasePoolPageData 构造方法,新构建的自动释放池子页面为 nil
    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
        parent(_parent), child(nil),
        depth(_depth), hiwat(_hiwat)
    {
    }
};

AutoreleasePoolPageData 内存结果示意图:

AutoreleasePoolPageData

magic_tAutoreleasePoolPageData 魔数,用于标识页面

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() {
        m[0] = M0;
        strncpy((char *)&m[1], M1, M1_len);
    }

    bool check() const {
        return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
    }

    bool fastcheck() const {
        return (m[0] == M0);
    }

#   undef M1
};

AutoreleasePoolPage 的初始化

AutoreleasePoolPage 构造函数

AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
    AutoreleasePoolPageData(begin(),
                            objc_thread_self(),
                            newParent,
                            newParent ? 1+newParent->depth : 0,
                            newParent ? newParent->hiwat : 0)
{ 
    if (parent) {
        parent->check();
        parent->unprotect();
        parent->child = this; //连接子页面
        parent->protect();
    }
    protect();
}

AutoreleasePoolPage 初始化

// App 启动,首次初始化时,父页面为 `nil`
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);

// 非首次初始化,需要传入父页面
page = new AutoreleasePoolPage(parentPage);

AutoreleasePoolPage 重载了 new 运算符,new 新的对象时分配额外内存,大小为 PAGE_MIN_SIZE(1<<12)。

//初始化分配内存,
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);
}

使用 sizeof 打印 AutoreleasePoolPage 大小,得到的内存大小不是 4096,而是 56,也就是 AutoreleasePoolPageData 的内部变量占用的内存大小。

(lldb) p sizeof(AutoreleasePoolPage)
(unsigned long) $2 = 56

AutoreleasePoolPage 函数实现

beginend

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

//end 方法cd
id * end() {
    return (id *) ((uint8_t *)this+SIZE);
}

AutoreleasePoolPage 初始化时,状态如下图所示,parent 指向父页面,child 为 nil,next 指向 begin() 函数指向的位置。

AutoreleasePoolPage_initial

full 与 empty

next 指针与 begin() 函数指向相同位置时,说明当前页面为一个空页面。

bool empty() {
    return next == begin();
}

nextend() 指向同一位置时,说明当前页面已满,需要重新创建新的页面。

bool full() { 
    return next == end();
}

向自动释放池中添加对象

id *add(id obj)
{
    unprotect();
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;
    protect();
    return ret;
}

向自动释放池添加对象时,移动 next 指针,如下图所示:

AutoreleasePoolPage_add

hotPage 与 coldPage

hotPage 指当前正在使用的页面

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 为父页面为 nil 的页面,也就是第一页。

static inline AutoreleasePoolPage *coldPage() 
{
    AutoreleasePoolPage *result = hotPage();
    if (result) {
        while (result->parent) {
            result = result->parent;
            result->fastcheck();
        }
    }
    return result;
}

push 方法

来看一下 AutoreleasePoolPage::push() 方法的真面目:

static inline void *push() 
{
    id *dest = autoreleaseFast(POOL_BOUNDARY);
    return dest;
}

push 方法中主要调用了 autoreleaseFast 方法,并且把 POOL_BOUNDARY 添加到自动释放池,POOL_BOUNDARY 为自动释放池的边界,实际上是一个 nil 对象。

#   define POOL_BOUNDARY nil

autoreleaseFast 方法实现:

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

首先获取当前 hotPage,接着分以下三种情形执行:

  1. 如果 hotPage 不为 nil,并且没有被填满,直接将对象添加到自动释放池(参见上面 add 方法)。

  2. 如果 hotPage 不为 nil,并且已被填满,则重新创建新的页面,并添加到当前页面链表上,最后同样调用 add 方法把 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.
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
    
        setHotPage(page);
        return page->add(obj);
    }
  3. 如果不存在 hotPage,则调用 autoreleaseNoPage,创建新页面,根据需要设置页面边界,最后添加 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
    
        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  &&  !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);
    }

pop 方法

static inline void
pop(void *token)
{
    AutoreleasePoolPage *page = pageForPointer(token);
    id *stop = (id *)token;
    return popPage(token, page, stop);
}

pageForPointer 根据传入的 token 获取自动释放池页面

static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
{
    AutoreleasePoolPage *result;
    uintptr_t offset = p % SIZE;

    result = (AutoreleasePoolPage *)(p - offset);
    result->fastcheck();

    return result;
}

popPage 实现主要代码如下:

static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
    page->releaseUntil(stop);

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

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 (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;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        if (obj != POOL_BOUNDARY) {
            objc_release(obj);
        }
    }

    setHotPage(this);
}

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

autorelease 方法

给一个对象发送 autorelease 方法,调用堆栈如下:

//这里直接摘自 draveness【自动释放池的前世今生.md】一文,https://git.io/Jta1i
- [NSObject autorelease]
└── id objc_object::rootAutorelease()
    └── id objc_object::rootAutorelease2()
        └── static id AutoreleasePoolPage::autorelease(id obj)
            └── static id AutoreleasePoolPage::autoreleaseFast(id obj)
                ├── id *add(id obj)
                ├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
                │   ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                │   └── id *add(id obj)
                └── static id *autoreleaseNoPage(id obj)
                    ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                    └── id *add(id obj)

autorelease 方法中调用了 _objc_rootAutorelease 方法

//NSObject.mm
- (id)autorelease {
    return _objc_rootAutorelease(self);
}

_objc_rootAutorelease 实现如下:

_objc_rootAutorelease(id obj)
{
    return obj->rootAutorelease();
}

objc_object 结构体中,rootAutorelease 方法调用 rootAutorelease2 方法

// Base autorelease implementation, ignoring overrides.
inline id objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

rootAutorelease2 调用了 AutoreleasePoolPage::autorelease 方法

id 
objc_object::rootAutorelease2()
{
    return AutoreleasePoolPage::autorelease((id)this);
}

AutoreleasePoolPage::autorelease(id) 方法,最终调用 autoreleaseFast 方法,将对象加入自动释放池

static inline id AutoreleasePoolPage::autorelease(id obj)
{
    id *dest __unused = autoreleaseFast(obj);
    return obj;
}

自动释放池调试

调用 Runtime 私有方法 _objc_autoreleasePoolPrint() 打印全部自动释放池信息

(lldb) po _objc_autoreleasePoolPrint();

输出结果如下:

objc[27435]: ##############
// 自动释放池与线程有关
objc[27435]: AUTORELEASE POOLS for thread 0x10b5eddc0
objc[27435]: 20 releases pending. // 池中有 20 个待释放的对象,三个页面

//第一个自动释放池页面,coldPage
objc[27435]: [0x7fb86d85c000]  ................  PAGE   (cold)
objc[27435]: [0x7fb86d85c038]  ################  POOL 0x7fb86d85c038 //自动释放池边界
objc[27435]: [0x7fb86d85c040]    0x600003758a40  _UIBoxedAutoreleasePoolMark
//第二个自动释放池页面
objc[27435]: [0x7fb86d85e000]  ................  PAGE   
objc[27435]: [0x7fb86d85e038]  ################  POOL 0x7fb86d85e038 //自动释放池边界
//第三个自动释放池页面,hotPage
objc[27435]: [0x7fb86d860000]  ................  PAGE  (hot) 
objc[27435]: [0x7fb86d860038]  ################  POOL 0x7fb86d860038
objc[27435]: [0x7fb86d860040]    0x600000e58750  UITraitCollection  autorelease count 3
objc[27435]: [0x7fb86d860048]    0x7fb86d406580  UIWindowScene
objc[27435]: [0x7fb86d860050]    0x7fb86e81d000  UINavigationController  autorelease count 13
objc[27435]: [0x7fb86d860058]    0x7fb86d6083d0  UIWindow  autorelease count 7
objc[27435]: [0x7fb86d860060]    0x60000114e640  NSURL
objc[27435]: [0x7fb86d860068]    0x60000095b300  __NSCFString
objc[27435]: [0x7fb86d860070]    0x600003510fe0  NSPathStore2
objc[27435]: [0x7fb86d860078]    0x60000114e3a0  NSURL
objc[27435]: [0x7fb86d860080]    0x600000c54380  __NSCFString
objc[27435]: [0x7fb86d860088]    0x600003b572a0  __NSArrayM
objc[27435]: [0x7fb86d860090]    0x600003b55a70  __NSArrayM
objc[27435]: [0x7fb86d860098]    0x7fb86e81d000  UINavigationController  autorelease count 2
objc[27435]: [0x7fb86d8600a0]    0x7fb86d6083d0  UIWindow  autorelease count 4
objc[27435]: [0x7fb86d8600a8]    0x600003b56d30  __NSArrayM
objc[27435]: [0x7fb86d8600b0]    0x600003b0b450  __NSArrayM  autorelease count 4
objc[27435]: [0x7fb86d8600b8]    0x7fb86d70fdb0  BBANewsFeedViewController  autorelease count 2
objc[27435]: ##############

自动释放池只有一个页面时,打印结果如下:

(lldb) po _objc_autoreleasePoolPrint();
objc[30760]: ##############
objc[30760]: AUTORELEASE POOLS for thread 0x1000dedc0
objc[30760]: 3 releases pending.
objc[30760]: [0x101818000]  ................  PAGE  (hot) (cold) //只有一个页面,即是 hotPage 又是 coldPage
objc[30760]: [0x101818038]  ################  POOL 0x101818038 // 自动释放池边界,nil
objc[30760]: [0x101818040]       0x100642e10  __NSCFString
objc[30760]: [0x101818048]       0x1010a2910  __NSArrayI
objc[30760]: ##############

_objc_autoreleasePoolPrint 实现

OBJC_EXPORT void _objc_autoreleasePoolPrint(void)

void _objc_autoreleasePoolPrint(void)
{
    AutoreleasePoolPage::printAll();
}

printAllAutoreleasePoolPage 的静态方法,用于打印输出全部自动释放池信息

static void AutoreleasePoolPage::printAll()
{
    _objc_inform("##############");
    _objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_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);


    for (page = coldPage(); page; page = page->child) {
        page->print();
    }

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

打印单个 AutoreleasePoolPage 信息方法,调用实例方法 print()

void AutoreleasePoolPage::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) {// 自动释放池边界,对象为 nil
            _objc_inform("[%p]  ################  POOL %p", p, p);
        } else {
            _objc_inform("[%p]  %#16lx  %s", // 待释放对象
                            p, (unsigned long)*p, object_getClassName(*p));
        }
    }
}

参考资料

Clone this wiki locally