autoreleasePool自动释放池
简介
自动释放池是OC中的一种内存回收机制,可以将加入autoreleasePool中的变量release时机延迟。当创建一个对象,在正常情况下,变量会在超出其作用域的时立即release。如果将对象加入到了自动释放池中,这个对象并不会立即释放,会等到runloop休眠/超出autoreleasepool作用域{}之后才会被释放。

程序从启动到加载完成,主线程对应的runloop会处于休眠状态,等待用户交互唤醒runloop。runloop在监听到交互事件之后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中,在一次完整的runloop结束之前会向自动释放池中的所有对象发送release消息,然后销毁释放池。
自动释放池本质是也是一个对象:
objc
@autoreleasePool{
}
//等价于
{
__AtAutoreleasePool __autoreleasepool;
}
objc
struct __AtAutoreleasePool {
//构造函数
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();//压入一个自动释放池边界
}
//析构函数
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);//弹出自动释放池,并释放池中登记的autorelease对象
}
void * atautoreleasepoolobj;//保存当前自动释放池的标记(一个池边界标记)
};
可以看到这个结构体有构造函数和析构函数,结构体定义的对象在作用域结束之后,会自动调用析构函数

可以看出,在ZLPerson创建的时候,自动调用了构造函数,在出了{}之后,自动调用了析构函数.
- autoreleasepool本质就是一个结构体对象,核心结构是AutoreleasePoolPage,
@autoreleasepool底层由一个或多个AutoreleasePoolPage组成,每个 Page 用来存放需要延迟释放的对象指针。页内从底地址到高地址依次存延迟释放的对象指针。 - 页的栈底是一个56字节的空间用于存储页头信息,用于运行时管理,一页总大小为4096字节(64位环境下)
- 只有第一页有哨兵对象,最多存储504个对象,从第二页开始最多存储505个对象
autoreleasepool在加入要释放的对象时,底层调用的是objc_autoreleasePoolPush方法autoreleasepool在调用析构函数释放时,内部调用objc_autoreleasePoolPop方法
我们先简单了解一下@autoreleasePool编译后的样子:
objc
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
}
///编译后:
void *token = objc_autoreleasePoolPush();
Person *p = [[[Person alloc] init] autorelease];
objc_autoreleasePoolPop(token);
- 自动释放池里存的不是对象本身,而是对象指针,整体是一个指针栈。
- 栈中的每个指针,要么是需要释放的对象,要么是
POOL_BOUNDARY,表示一个自动释放池边界。 - pool token 是指向当前池边界标记的指针。
- 当池子被 pop 时,所有比哨兵标记更新的对象都会被释放。
- 自动释放池这个指针栈被分成一页一页的内存块,每一页是一个
AutoreleasePoolPage,多个 Page 之间通过双向链表连接。 - 线程本地存储 TLS 指向 hot page,新加入的 autorelease 对象会存到这个 hot page 中。
autoreleasePoolPage
objc
void *
_objc_autoreleasePoolPush(void)
{
return objc_autoreleasePoolPush();
}
void
_objc_autoreleasePoolPop(void *ctxt)
{
objc_autoreleasePoolPop(ctxt);
}
可以看出底层调用的是autoreleasePoolPage的push和pop方法,下面是page定义:可以看出,自动释放池是一个页,大小为4096字节
objc
//************宏定义************
#define PAGE_MIN_SIZE PAGE_SIZE
#define PAGE_SIZE I386_PGBYTES
#define I386_PGBYTES 4096 /* bytes per 80386 page */
//************类定义************
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
public:
//页的大小
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MIN_SIZE; // size and alignment, power of 2
#endif
private:
...
//构造函数
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),//开始存储的位置
objc_thread_self(),//传的是当前线程,当前线程时通过tls获取的
newParent,
newParent ? 1+newParent->depth : 0,//如果是第一页深度为0,往后是前一个的深度+1
newParent ? newParent->hiwat : 0)
{...}
//...省略一堆的操作页的代码
可以看出autoreleasePoolPage是继承与AutoreleasePoolPageData,下面我们看一下这个AutoreleasePoolPageData定义:
objc
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
//用来检查AutoreleasePoolPage的结构是否完整
magic_t const magic;//16个字节
//指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()
__unsafe_unretained id *next;//8字节
//指向当前线程
pthread_t const thread;//8字节
//指向父节点,第一个结点的parent值为nil
AutoreleasePoolPage * const parent;//8字节
//指向子节点,最后一个结点的child值为nil
AutoreleasePoolPage *child;//8字节
//表示深度,从0开始,往后递增1
uint32_t const depth;//4字节
//表示high water mark 最大入栈数量标记
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)
{
}
};
autoreleasepoolPageData是autoreleasePoolPage的数据基类,主要保存每一页自动释放池的页头信息,包括magic、next、thread、parent、child、depth等信息,autoreleasepoolpage通过private继承autoreleasePoolPageDat复用这些字段,并在此基础上实现自动释放池的核心逻辑。因此,
AutoreleasePoolPageData可以理解为数据层,AutoreleasePoolPage可以理解为真正执行管理逻辑的 Page 对象。一个完整的AutoreleasePoolPage内存布局中,前面是AutoreleasePoolPageData页头,后面才是用来存放POOL_BOUNDARY和 autorelease 对象指针的栈空间。
objc_autoreleasePoolPush
源码如下:
objc
//入栈
static inline void *push()
{
id *dest;//表示这次压栈的位置
if (slowpath(DebugPoolAllocation)) {//开启一个优化,决定师傅重新开始一页
// Each autorelease pool starts on a new pool page.自动释放池从新池页面开始
//如果没有,则创建
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
//压栈一个POOL_BOUNDARY,即压栈哨兵
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
判断是否有pool,如果没有,则通过autoreleaseNewPage方法创建。如果没有,则通过autoreleaseFast压栈哨兵对象。
创建页autoreleasenNewPage
objc
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();//寻找当前是否有正在使用的自动释放池页
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;//判断取出的是否是一个特殊的占位符(不是真的autoreleasePoolPage),只是表示当前线程已经push了一个自动释放池,但是池中没有任何对象,暂时不分配page
if (result) result->fastcheck();//快速校验当前page是否有效
return result;
}
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
ASSERT(!hotPage());//当前线程没有任何可用的page
bool pushExtraBoundary = false;//决定真正创建page之后是否需要往额外补充一个哨兵
if (haveEmptyPoolPlaceholder()) {//如果当前已经push过一个自动释放池,但是没有真正创建page时,需要额外压栈一个哨兵。
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {//没有池,但是autorelease了对象
_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) {//第一次push自动释放池,但不马上创建page
return setEmptyPoolPlaceholder();//在TLS中放一个EMPTY_POOL_PLACEHOLDER,节省内存
}
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
return page->add(obj);
}
autoreleasepoolPage构造方法
objc
//**********AutoreleasePoolPage构造方法**********
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),//开始存储的位置
objc_thread_self(),//传的是当前线程,当前线程时通过tls获取的
newParent,
newParent ? 1+newParent->depth : 0,//如果是第一页深度为0,往后是前一个的深度+1
newParent ? newParent->hiwat : 0)
{
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
//this 表示 新建页面,将当前页面的子节点 赋值为新建页面
parent->child = this;
parent->protect();
}
protect();
}
- begin()表示压栈的位置,其具体位置等于页首地址+56,这个56就是结构体AutoreleasePoolPageData的内存大小
objc_thread_self()表示的是当前线程,而当前线程时通过tls获取的
objc
__attribute__((const))
static inline pthread_t objc_thread_self()
{
//通过tls获取当前线程
return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}
- newParent表示父节点
接下来我们查看一下释放池内存结构
首先我们先在Build Settings -> Objectice-C Automatic Reference Counting切换为MRC模式:
然后测试如下:

这里只输出了四个对象是因为编译器/runtime 对 autorelease 返回值做了优化,也就是 autorelease return value optimization 。它可能让某些 autorelease 不真正入池,而是通过运行时的快速返回值机制交接对象。objc runtime 里确实有 objc_autoreleaseReturnValue、objc_retainAutoreleasedReturnValue、objc_unsafeClaimAutoreleasedReturnValue 这一套优化入口

autoreleaseFast压栈对象
objc
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);
}
}
autoreleaseFullPage
如果当前页已经存储满了,则通过do-while循环查找子节点对应的页,如果不存在就新建页,并压入对象。
objc
//添加自动释放对象,当页满的时候调用这个方法
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
ASSERT(page == hotPage());
ASSERT(page->full() || DebugPoolAllocation);
//do-while遍历循环查找界面是否满了
do {
//如果子页面存在,则将页面替换为子页面
if (page->child) page = page->child;
//如果子页面不存在,则新建页面
else page = new AutoreleasePoolPage(page);
} while (page->full());
//设置为当前操作页面
setHotPage(page);
//对象压栈
return page->add(obj);
}
add方法
objc
id *add(id obj)
{
ASSERT(!full());
unprotect();//自动释放池的一套调试保护机制,当页处于保护状态时,可能会通过内存白虎、防御性检查等方式防止外部非法修改。所以这里需要先解除保护才能修改
//传入对象存储的位置
id *ret = next; // faster than `return next-1` because of aliasing
//将obj压栈到next指针位置,然后next进行++,即下一个对象存储的位置
*next++ = obj;
protect();
return ret;
}
通过next指针存储释放对象,并将next指针递增来添加释放对象
autorelease底层分析
objc
__attribute__((aligned(16), flatten, noinline))//16位对齐、对内对外内联、对外相反
id
objc_autorelease(id obj)//autorelease的C函数入口
{
//不是对象,则直接返回
if (!obj) return obj;
//如果是小对象,也直接返回
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
inline id
objc_object::autorelease()//先判断类有没有自定义的retain、release、autorelease相关方法
{
ASSERT(!isTaggedPointer());
//判断是否是自定义类
if (fastpath(!ISA()->hasCustomRR())) {
return rootAutorelease();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));//发现有自定义的逻辑,走消息转发
}
inline id
objc_object::rootAutorelease()//默认autorelease实现
{
//如果是小对象,直接返回
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;//判断是否开启了返回值优化,避免不必要的入池和引用计数操作
return rootAutorelease2();//真正准备将对象加入自动释放池(慢路径)
}
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
static inline id autorelease(id obj)
{
ASSERT(obj);
ASSERT(!obj->isTaggedPointer());
//autoreleaseFast 压栈操作
id *dest __unused = autoreleaseFast(obj);//尝试将对象快速加入当前线程池的自动释放池
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
调用路径:objc_autorelease(obj) → objc_object::autorelease() → rootAutorelease() → rootAutorelease2() → AutoreleasePoolPage::autorelease() → autoreleaseFast(obj)
无论是压栈哨兵对象还是普通对象,都会进入到autoreleaseFase方法。
autoreleasePoolPop分析
objc
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;//表示本次pop要停止的位置
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {//如果 token 是 EMPTY_POOL_PLACEHOLDER,说明 push 的时候没有真正创建 Page
page = hotPage();//查看当前有没有真正的page
if (!page) {
return setHotPage(nil);//表示当前空池结束,将线程的hotPage状态清空
}
page = coldPage();
token = page->begin();//如果顶层 pool 使用的是 EMPTY_POOL_PLACEHOLDER,后来又创建了 Page,那么 pop 顶层 pool 时,需要从整个 Page 链的起点开始处理
} else {
page = pageForPointer(token);//根据token找到它处于哪一个autoreleasePooloage中
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {//特殊情况,就算不是哨兵,但是是最冷page的起始位置也允许
} else {
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);//调试出栈
}
return popPage<false>(token, page, stop);
}
EMPTY_POOL_PLACEHOLDER是延迟创建策略,push阶段先进行占位,autorelease阶段发现时再补建。(替换)
popPage
objc
template<bool allowDebug>
static void popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();//输出曾经使用过的最大容量,只和调试有关
page->releaseUntil(stop);//从当前位置一直释放到终止位置
if (allowDebug && DebugPoolAllocation && page->empty()) {//当前页清空
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
}
else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {//已经清空到个最顶层池,直接删掉当前页并将hotpage置空
page->kill();
setHotPage(nil);
}
else if (page->child) {//滞后保留策略:runtime 会保留一部分空 page,供下次继续使用
if (page->lessThanHalfFull()) {//使用量不大,都删了
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();//使用量比较大,保留一个子页
}
}
}
void releaseUntil(id *stop)
{
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
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
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
//最后的池边界标记会被弹出
- 自动释放池的压栈(即push)操作中:
当没有pool,即只有空占位符(存储在tls中)时,则创建页,压栈哨兵对象
在页中压栈普通对象主要是通过next指针递增进行的,
当页满了时,需要设置页的child对象为新建页 - 出栈操作中
通过next指针递减来实现一个释放
当页空了时,需要赋值页的parent对象为当前页