autoreleasepool前言
下面是我们自己创建了一个autoreleasepool,创建的对象有自动添加到自动释放池,和手动添加到释放池的。当我们自己创建了autoreleasepool然后把对象添加进去,这个就是手动添加到释放池。每个autoreleasepool都会对应一个runloop。如果手动添加到autoreleasepool里这个就和runloop没有关系了,出了autoreleasepool作用域就会被释放。
通过下图的写法发现内存会暴增
通过下图的写法发现内存不会暴增,因为我们在其位置上添加了autoreleasepool,其出了这个autoreleasepool就会被释放掉。
AutoreleasePool底层:
首先我们写个使用@autoreleasepool的释放池,代码如下,然后进行clang命令进行一个操作。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
clang后的代码,在这里点击__AtAutoreleasePool进去发现其是一个struct的结构体。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_64_v4jdthx95753k1gbfyy30w0w0000gn_T_main_cf4d78_mi_0);
}
return 0;
}
__AtAutoreleasePool构造函数和析构函数。
struct __AtAutoreleasePool {
构造函数 __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
析构函数 ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
通过上面其实AutoreleasePool底层代码可以写成这几个:
objc_autoreleasePoolPush()
NSLog(@"Hello, World!")
objc_autoreleasePoolPop(atautoreleasepoolobj)
我们通过汇编调试也可以看到AutoreleasePool其实其是走objc_autoreleasePoolPush和objc_autoreleasePoolPop方法
objc_autoreleasePoolPush方法
下面是objc_autoreleasePoolPush执行的代码,其中::符号是一个命名空间,就是说明AutoreleasePoolPage去调用push()方法
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
下面是官方对自动释放池的介绍:
Autorelease pool implementation
A thread's autorelease pool is a stack of pointers.
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.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.
A thread's autorelease pool is a stack of pointers.
线程的⾃动释放池是⼀个指针堆栈。(说明⾃动释放池肯定和线程是有关联的)
Each pointer is either an object to release, or POOL_BOUNDARY which is an
autorelease pool boundary.
每个指针要么是将要释放的对象,要么是⼀个POOL_BOUNDARY,作为⾃动释放池的边界。
(⾃动释放池⾥⾯的数据有俩种类型,⼀种是将要被释放的对象,另⼀种是
POOL_BOUNDARY,超出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.
有⼀个指向该池的 POOL_BOUNDARY 的指针。当池被弹出时,每个⽐哨兵更热的对象都会被
释放。(在调⽤pop函数的时候,整个⾃动释放池⾥⾯的对象都会被释放。)
The stack is divided into a doubly-linked list of pages. Pages are added and
deleted as necessary.
栈被分成⼀个双向链接的⻚⾯列表。根据需要添加和删除⻚⾯。(⾃动释放池是⼀个双向列表
的栈结构。它的每⼀个节点都是AutoreleasePoolPage。)
Thread-local storage points to the hot page, where newly autoreleased objects
are stored.
线程本地存储指向热⻚,其中存储了新的⾃动释放对象。(⾃动释放池⾥⾯的数据也有线程缓
存)
下面是AutoreleasePoolPage里的data数据,其内部里是存储一些数据。
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
struct AutoreleasePoolEntry {
uintptr_t ptr: 48;
uintptr_t count: 16;
static const uintptr_t maxCount = 65535; // 2^16 - 1
};
static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif
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)
{
}
};
下面是AutoreleasePoolPageData里的成员变量意思:
magic 用来校验 AutoreleasePoolPage 的结构是否完整;
next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() ;
thread 指向当前线程;
parent 指向父结点,第一个结点的 parent 值为 nil ;
child 指向子结点,最后一个结点的 child 值为 nil ;
depth 代表深度,从 0 开始,往后递增 1;
hiwat 代表 high water mark 最大入栈数量标记
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 inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}
下面的是POOL_BOUNDARY也就是一个哨兵(⾃动释放池的边界)代码。
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();
}
当第一次进来时候,会进行AutoreleasePoolPage进行创建,然后把其加入到hotPage里,hotPage也就是我们进行设置的页。 page->add(POOL_BOUNDARY)就是把哨兵对象添加到page里。
// 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);
当AutoreleasePoolPage不是第一次创建时候就会走下面的autoreleaseFast方法进行创建。也是通过hotPage进行创建,然后判断page是否满了,如果没有满了的话就进行一个添加到page里。
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);
}
}
AutoreleasePoolPageData使用
下面我们看到AutoreleasePoolPage其实就是调用AutoreleasePoolPageData里的数据,在AutoreleasePoolPageData里有个begin()参数,这个是begin()点进去其实是一个内存平移的操作。
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
直接在autoreleasepool里创建p对象是不会进入到autoreleasepool进行管理的,其是通过ARC进行控制其释放和销毁。通过alloc,new ,copy,mutablecopy,allocwith这些创建的对象在autoreleasepool里是不会放到autoreleasepool里进行管理的。 那如果我们要添加到自动释放池,需要在对象前面加__autoreleasing,这样就会受autoreleasepool释放池进行管理( __autoreleasing HPWPerson *p = [ [HPWPerson alloc] init])。其中我们也可以通过系统提供的_objc_autoreleasePoolPrint()进行打印释放池里的对象。如果是通过stringwithformat这类创建的话就会自动添加到autoreleasepool里无需在代码前加入__autoreleasing,其中需要注意stringwithformat后面的string要大于9位数,如果小于等于9位数,那么这个就是taggerPoint小对象了,这个是不会添加到autoreleasepool里的。我们可以通过_objc_autoreleasePoolPrint函数进行打印验证。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
HPWPerson *p = [ [HPWPerson alloc] init];
}
return 0;
}
下面是通过_objc_autoreleasePoolPrint的打印,发现其值添加到page里了,其中用的是__autoreleasing进行修饰。
下面是没有__autoreleasing进行修饰,发现其没有加入到autoreleasepool里
其实添加__autoreleasing这个也就是在底层操作调用obje_autorelease,在其里面最终调用的是autroreleaseFast这个函数。autroreleaseFast这个函数我们之前说过,其会调用创建page然后进行一个添加操作,也就是添加到autoreleasepool里。
思考1:当我们把下面红圈里的代码放到我们手写的自动释放池autoreleasepool外,想想这句代码会被放到自动释放迟里吗?
我们打印后发现其String代码还是被加到了自动释放迟中,这个是为什么呢,其实这个是和runloop有关系,当我们程序运行的时候,其就会有开启一个runloop,这个runloop会自动的帮我们创建自动释放池。
下面是runloop运行的示意图,当APP启动的时候,主线程的runloop是自动开启的,在循环的时候,其会注册两个监听,一个监听是当我们runloop即将进入的时候(也或是从休眠状态进入的时候),就会 创建一个自动释放池,这个优先级最高。当要休眠的时候,其也会有一个监听的函数,监听runloop休眠的时候,也就是等runloop里任务完成后,也就是一次循环结束之前就会倾倒里面的释放池,销毁里面的对象和变量,这个优先级别是最低的,这个是因为其要等runloop里其他事件都完成后才进行此操作。
通过上面的解释也就知道了为什么我们写在主线程里自己创建的autoreleasepool外面的程序也会被添加到自动释放池里。
上面我们是写在主线程中的,那如果我们写在子线程中,其会被加入到autoreleasepool中吗?下面我们看一个官方文档的介绍:
上面也就是说,当放在子线程中时候,其也会放到子线程的autoreleasepool中,和主线程中的一致。
总结:子线程的autoreleasepool什么时候销毁,其是在子线程销毁的时候会释放其autoreleasepool,这个是在子线程中没有开启runloop的时候。 如果子线程开启了runloop也就和我们之前说的主线程那个一样了。
autoreleasepool对象在ARC中什么时候释放总结分两种情况
1.手动添加自动释放池里的对象,对象是出了autoreleasepool作用域就会释放
2.系统自动添加到释放池的autoreleasepool对象,这种情况下也要分两种,
1)如果在主线程中或者是在子线程中开启了runloop那就在runloop即将休眠的时候进行释放掉(如界面不动时候runloop也就会休眠)
2)如果在子线程中没有开启runloop,那就在子线程销毁的时候被释放。
下面我们探讨下一个hotPage的大小
一个hotPage的大小是4k,如下图1左移动12位就位4096,也就是4k
那我们如何验证呢,4096 - 56(本身大小)- 8(哨兵对象) 为 4032。 4032/8 一个hotpage可存504个对象。下面就是对应的验证,我们创建505个对象,就会发现在505的地方会再开启一个新page,当前的page后面会显示hot,而前面放了504个对象的page就会显示cold。如果我们把505改成1009,大家想想会存多少,和开启几个page,其实会开启两个hotpage,第一个存504个对象,第二个存505个对象,这个是因为一个autorelease只有一个哨兵对象(占8字节),所以第二个hotpage就可以存505个对象。
上面这些操作都是自动释放池push操作,那自动释放池pop操作又是做了些啥呢,我们下面看到再pop时候传了一个参数,其实这个参数就是之前push进来的哨兵对象。
下面的token就是传进来的哨兵对象,哨兵对象是放在第一位的,通过这个哨兵对象找到哨兵对象所在的page。
下面的popPage里releaseUntil是进行释放,释放到哨兵对象为止,会从最后面的hot页往前面找,找到哨兵对象所在的page为止,然后调用objc_release(obj)进行释放。
那自动释放池我们可以用来做什么呢,可以防止内存短时间内爆涨
其是一个内存自动回收机制,如下,当我们把autoreleasepool去掉的时候,其内存会爆涨。
当我们一同autoreleasepool去包裹的时候,其内存变化就会很小,因为得到了及时的释放