内存管理-AutoreleasePool
AutoreleasePool(自动释放池)是Objective-C中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。
@autoreleasepool { ... }可以通过 clang -rewrite-objc 命令转成C++代码如下
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
然后可以从objc源码中找到__AtAutoreleasePool的定义
struct __AtAutoreleasePool {
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
这个结构体先通过构造函数构(__AtAutoreleasePool)创建一个栈变量__autoreleasepool,然后在大括号结束的时候通过析构函数(~__AtAutoreleasePool)销毁__autoreleasepool变量,这里利用了变量声明和自动变量在代码块结束后自动销毁的特性。所以@autoreleasepool { ... }相当于以下代码
{
atautoreleasepoolobj = objc_autoreleasePoolPush();
//AutoreleasePool中的其他代码
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
这里分别调用了 objc_autoreleasePoolPush 和 objc_autoreleasePoolPop 函数。在源码中可以找到函数实现
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
可以看到这里分别调用了AutoreleasePoolPage的push和pop类方法。而AutoreleasePoolPage继承于AutoreleasePoolPageData的结构体。
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(__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)
{
}
};
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
//省略
}
下面看下AutoreleasePoolPageData每一个成员变量的作用
magic:magic_t类型,是用来检查 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() {
OBJC_ASSERT(M1_len == strlen(M1));
OBJC_ASSERT(M1_len == 3 * sizeof(m[1]));
m[0] = M0;
strncpy((char *)&m[1], M1, M1_len);
}
~magic_t() {
volatile uint64_t *p = (volatile uint64_t *)m;
p[0] = 0; p[1] = 0;
}
}
可以看出构造的时候
m[0]= 0xA1A1A1A1
m[1-3] = AUTORELEASE!
检查的时候方法如下,判断第一个字节是0xA1A1A1A1, 后面三个字节是AUTORELEASE! 如下
bool check() const {
return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
}
next:类型是 __unsafe_unretained id *,存放的是下一个被 autorelease 对象的指针存放的地址。
thread:自动释放池对应的线程。
parent和child:用来保存前一个 AutoreleasePoolPage 和后一个 AutoreleasePoolPage,双向链表结构。
depth:链表长度
hiwat:一个在 DEBUG 时才有用的参数,表示最高记录过多少对象。
下面看下这些变量是怎么存储的,会发现AutoreleasePoolPage 重载了 new 操作符
#define PAGE_MIN_SHIFT 12
#define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)
static size_t const SIZE = PAGE_MIN_SIZE;
static void * operator new(size_t size) {
return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
}
这样一个新的AutoreleasePoolPage对象就需要 SIZE大小,可以看出SIZE的大小为4096,而AutoreleasePoolPage的成员变量大小加在一起也只有 56 字节,剩下多出的就是存储autorelease对象的地方。如下图
图片中 begin、end方法定义如下
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
接下来看AutoreleasePoolPage::push()方法,定义如下
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;
}
当slowpath(DebugPoolAllocation)的时候,会调用autoreleaseNewPage。也就是当Xcode中设置环境变量OBJC_DEBUG_POOL_ALLOCATION为YES的时候。
OPTION( DebugPoolAllocation, OBJC_DEBUG_POOL_ALLOCATION, "halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools")
EMPTY_POOL_PLACEHOLDER和POOL_BOUNDARY的定义如下
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
# define POOL_BOUNDARY nil
其他时候调用autoreleaseFast
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
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);
}
}
两个方法不同的地方是,Debug的时候会返回一个全新的AutoreleasePoolPage。
看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;
}
这里是从tls中获取之前存入的page,tls是线程局部存储(Thread Local Storage)的缩写。如果是EMPTY_POOL_PLACEHOLDER的话,返回nil,后面会具体说明其作用。存入的方法如下
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
继续看autoreleaseFullPage方法
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);
}
意思是,初始化个Page,添加为现有Page的子节点,再设置成hotPage,然后添加对象。
最后是autoreleaseNoPage
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",
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);
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);
}
可以看出,因为push的时候传的是POOL_BOUNDARY,这里并没有初始化新的page,而是设置了个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;
}
等有再有autorelease对象进来的时候,先判断有没有占位符,然后再把初始化page并且设置为hotPage,并把POOL_BOUNDARY添加到其中,最后再添加对象到page中。
对象的autorelease最终是调用到AutoreleasePoolPage的autorelease方法
static inline id autorelease(id obj)
{
ASSERT(!_objc_isTaggedPointerOrNil(obj));
id *dest __unused = autoreleaseFast(obj);
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
#else
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
#endif
return obj;
}
可以看出,这里主要是调用了autoreleaseFast方法,更上面push的时候调用的方法是一样的,只不过这里obj是对象地址,push的时候是POOL_BOUNDARY。
最后就是pop方法
static inline void
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<false>(token, page, stop);
}
这里面主要是通过token获取需要pop的Page,真正的操作在popPage方法,模板传值为false,所以去除Debug相关的逻辑
template<bool allowDebug>
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();
AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;
// create an obj with the zeroed out top byte and release that
id obj = (id)entry->ptr;
int count = (int)entry->count; // grab these before memset
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
// release count+1 times since it is count of the additional
// autoreleases beyond the first one
for (int i = 0; i < count + 1; i++) {
objc_release(obj);
}
}
}
setHotPage(this);
}
大概逻辑上,向下遍历,然后通过内存平移获取对象,判断如果不是哨兵对象,会调用objc_release的操作。最后设置当前页面为hotPage。
最后看下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);
}
上面是把对象都释放了,这里把不需要的page设置为nil。
综上所述:
1、自动释放池的压栈和出栈,是通过结构体的构造函数和析构函数触发的,压栈调用objc_autoreleasePoolPush函数,出栈调用objc_autoreleasePoolPop函数。
2、自动释放池是一个存储指针的栈结构,指针是一个要释放的对象或者是POOL_BOUNDARY自动释放池边界。
3、池页大小为4096字节,每一页都包含56字节的成员变量,一个Page最多可以容纳 (4096-56) / 8 = 505个autorelease的对象。
可以写代码验证下,写之前要先把工程设置为MRC。
void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (int i = 0; i < 505; i++) {
NSObject *obj = [[[NSObject alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
}
return 0;
}
输入结果如下
objc[11901]: [0x101010000] ................ PAGE (full) (cold)
objc[11901]: [0x101010038] ################ POOL 0x101010038
objc[11901]: [0x101010040] 0x100c277e0 NSObject
objc[11901]: [0x101010048] 0x100c272e0 NSObject
objc[11901]: [0x101010050] 0x100c26ba0 NSObject
objc[11901]: [0x101010058] 0x100c26160 NSObject
......
objc[11927]: [0x10100d000] ................ PAGE (hot)
objc[11927]: [0x10100d038] 0x100ab2920 NSObject
可以看出,第一个是POOL_BOUNDARY,505个对象循环加入到page中,最后一个的时候page满了,所以最后一个加到了新的page中。