一、autoreleasepool调试
1. 加断点
2. 打开汇编查看堆栈信息:
打开汇编Debug:Debug -> Debug Workflow -> Always Show Disassembly
我们会看到调用了objc_autoreleasePoolPush
和objc_autoreleasePoolPop
,并且明显是在objc源码里边,下一步我们就需要在底层源码中找到它们。但是在探索push和pop之前我们需要先了解 AutoreleasePoolPage
是干什么的。
二、AutoreleasePoolPage
1. AutoreleasePoolPage结构
AutoreleasePoolPage
继承自结构体 AutoreleasePoolPageData
:
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
// ... 省略 ...
}
/**
Autorelease pool implementation
// 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.
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.
// 3. 双向链表
The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.
// 4. Thread-local storage线程本地存储
Thread-local storage points to the hot page, where newly autoreleased objects are stored.
*/
// 所有的 AutoreleasePool 对象,都会有如下属性
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic; // 16
__unsafe_unretained id *next; // 8
pthread_t const thread; // 8
AutoreleasePoolPage * const parent; // 8
AutoreleasePoolPage *child; // 8
uint32_t const depth; // 4
uint32_t hiwat; // 4
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)
{
}
};
- magic 用来校验 AutoreleasePoolPage 的结构是否完整;
- next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向begin() ;
- thread 指向当前线程;
- parent 指向父结点,第一个结点的 parent 值为 nil ;
- child 指向子结点,最后一个结点的 child 值为 nil ;
- depth 代表深度,从 0 开始,往后递增 1;
- hiwat 代表 high water mark 最大入栈数量标记
从上面我们可以知道自动释放池是一种栈结构(先进后出),每一个自动释放池都是由多个AutoreleasePoolPage组成的,而且AutoreleasePoolPage是以双向链表的数据结构连接起来的。
2. AutoreleasePoolPage容量
创建第一个 page
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
{
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
此处this
大小是56,当前结点从56开始往下面添加。
下面我们用一个 i < 5
的for循环来给自动释放池添加对象:
经过不断测试,我们找到505是一个分界线:
总结:AutoreleasePoolPage其中的前56字节存储成员变量,其余区域存储加载到自动释放池中的对象,并且AutoreleasePoolPage每505个对象为一页,每到505的倍数会重新分页。只有第一页是504对象,因为边界对象POOL_BOUNDARY(8字节)占一位。
三、objc_autoreleasePoolPush
1. objc_autoreleasePoolPush
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
2. push方法
push是一个压栈操作,POOL_BOUNDARY为栈顶,调试实际autoreleaseFast
为主要方法:
static inline void *push()
{
id *dest;
if (slowpath(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;
}
3. autoreleaseFast方法
- 第一步判断当前 page 存在且没有满时,直接将对象添加到当前 page ,即 next 指向的位置;
- 第二步判断当前 page 存在且已满时,创建一个新的 page ,并将对象添加到新创建的 page 中;
- 第三步判断当前 page 不存在时,即还没有 page 时,创建第一个 page ,并将对象添加到新创建的 page 中。
- hotPage标记当前AutoreleasePoolPage
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);
}
}
4. add方法
把对象添加到 AutoreleasePoolPage
中,并且移动指针指向下个对象
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj; // 移动next指针指向下一个对象
protect();
return ret;
}
5. autoreleaseFullPage方法
如果当前hot page页满了,创建新页,并设置新页为hot page,添加对象到新页中。
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);
}
6. autoreleaseNoPage
如果内存中没有AutoreleasePoolPage,初始化AutoreleasePoolPage,parent为nil。标记当前页为hot page,添加边界POOL_SENTINEL(哨兵) 对象,然后将传入对象添加到AutoreleasePoolPage。
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",
objc_thread_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); // 创建第一个AutoreleasePoolPage
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY); // 添加POOL_BOUNDARY对象
}
// Push the requested object or pool.
return page->add(obj);
}
四、objc_autoreleasePoolPop
1. objc_autoreleasePoolPop
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
2. pop方法
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
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 (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage(token, page, stop);
}
3. popPage方法
releaseUntil
递归释放对象,然后 kill()
一层层杀空的表,最后 setHotPage
设置hotPage指向上一页直到为nil
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
// 递归release
page->releaseUntil(stop);
// 释放对象,一层层杀空的表,向前移动hotPage
if (allowDebug && DebugPoolAllocation && page->empty()) {
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
page->kill();
setHotPage(nil);
} else if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
4. 递归release
// 简化版
void releaseUntil(id *stop)
{
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);
}
5. 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);
}
6. objc_release
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release(); // release对象
}
五、总结
- 进作用域空间调用
objc_autoreleasePoolPush
压栈,出作用域空间调用objc_autoreleasePoolPop
像栈中对象发送release
释放来出栈 - 对象调用
autorelease
时,会将对象压栈到AutoreleasePoolPage
中 -
@autoreleasepool
与线程关联,一个@autoreleasepool
对应一个线程 -
@autoreleasepool
嵌套只会创建一个page,但会有两个哨兵(POOL_BOUNDARY
)