autoreleasepool
:自动释放池。在aotureleasepool
中创建的对象,在自动释放池销毁时,对所有的对象做release
操作。
一般在程序的入口都会有如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
}
return 0;
}
在这里 @autoreleasepool{}
,编译器会在大括号前后插入两行代码:
objc_autoreleasePoolPush();
// 大括号之间的代码
objc_autoreleasePoolPop();
那么此处objc_autoreleasePoolPush();
和 objc_autoreleasePoolPush();
都做了哪些事情呢?
源码解读
查看objc4-818.2
源码,由于源码中autorelease
被放到了NSObject.mm文件中,该文件又比较大,所以以下代码皆是做了简化的代码。
在看具体的autoreleasepool
之前,我们先看下苹果官方对于autoreleasepool
的介绍
/***********************************************************************
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.
**********************************************************************/
线程的自动释放池是指针的堆栈。
每个指针都是要释放的对象,或者是POOL_BOUNDARY,它是自动释放池的边界。
自动释放池的token是指向该池的POOL_BOUNDARY的指针。当池被弹出时,优先释放对象,其次是哨兵。
堆栈分为两个双向链接的页面列表。 根据需要添加和删除页面。
TLS变量存储在热页面,该页面存储新自动释放的对象。
注:TLS(线程本地存储)
是一种在多线程时使用的技术,它可以使你的全局变量、静态变量以及局部静态、静态成员变量成为线程独立的变量,即每个线程的TLS变量之间互不影响。例如:linux下的全局变量 errno,windows下的GetLastError ,线程A在设置了一个错误信息后,线程B又设置了一个错误信息,前一个线程设置的信息就被覆盖了。解决方法就是将这个全局变量设置为TLS变量,这样在用户看来errno是一个全局变量,实际上它是每个线程独立的。
AutoreleasePoolPage的介绍:
AutoreleasePoolPageData
定义
struct AutoreleasePoolPageData
{
//检查校验完整性的变量。
magic_t const magic;
//指向新加入的autorelease对象。
__unsafe_unretained id *next;
//当前page所在的线程。
pthread_t const thread;
//指向前一个page的指针。
AutoreleasePoolPage * const parent;
//指向后一个page的指针。
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)
{
}
};
AutoreleasePoolPage
定义
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is
// pushed and it has never contained any objects. This saves memory
// when the top level (i.e. libdispatch) pushes and pops pools but
// never uses them.
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
# define POOL_BOUNDARY nil
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:
static pthread_key_t const key = 38;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const COUNT = SIZE / sizeof(id);
}
这里AutoreleasePoolPage与AutoreleasePoolPageData有一个继承关系,AutoreleasePoolPageData主要存放结构数据;
-
EMPTY_POOL_PLACEHOLDER
占位的空池子):当一个pool恰好被push并且它绝不包含任何对象的时候,EMPTY_POOL_PLACEHOLDER存储在TLS(Thread-local storage 存储本地的线程)中。当顶层(即libdispatch)推送和弹出pools,却没有使用这些pools的时候,可以节省内存开销。 -
POOL_BOUNDARY
边界值,是一个nil对象; -
SIZE
AutoreleasePoolPage的大小,一般是4K; -
COUNT
一个page里对象数
根据其结构数据,我们可以看出AutoreleasePool
整体结构是一个由AutoreleasePoolPage
为节点构成双向列表,其中parent
和child
分别指向父节点和子节点。next
则指向节点内部的需要autorelease
的对象。
搜索objc_autoreleasePoolPush
我们便可看到整个autoreleasepool的入口:
这里主要通过三个函数来分析Autoreleasepool的核心功能
objc_autoreleasePoolPush
void *
objc_autoreleasePoolPush(void)
{
return 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);
printf("%p\n", dest);
return dest;
}
DebugPoolAllocation
判断是否是调试模式,这里涉及到两个方法autoreleaseNewPage
和autoreleaseFast
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;
if (result) result->fastcheck();
return result;
}
static inline void *tls_get_direct(tls_key_t k)
{
// ASSERT(is_valid_direct_key(k));
if (_pthread_has_direct_tsd()) {
return _pthread_getspecific_direct(k);
} else {
return pthread_getspecific(k);
}
}
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
当在调试模式时,调用hotPage
来获取AutoreleasePoolPage;hotPage中通过tls_get_direct
来获取tls线程所维护的hotpage;
1、上文中也提到TLS线程,TLS是通过线程key 去获取该线程所维护的hotPage,
2、如果把hotPage看做是一个全局的变量,那么在多线程操作中,多线程更改参数就会造成数据污染;所以就用到了TLS变量;
3、如果直接获取TLS变量,在有大量使用TLS变量的地方,寻址所消耗的时间将会增加一倍,所以一般使用内联函数
4、对照setHotPage
发现tls_set_direct
传入的为key-value,即pthread_key_t
线程key
值与当前AutoreleasePoolPage
;那么在取的时候,取的也就是当前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);
}
}
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);
}
id *autoreleaseNoPage(id obj)
{
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;
}
// 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);
}
快速获取自动释放池,先通过hotPage获取,也就是tls线程关联的page;
这里有三种情况:
- 如果hotpage有值,并且还没满,直接
add
obj - 如果hotpage有值,但是当前page满了,调用
autoreleaseFullPage
,并传入obj与当前hotpage;在autoreleaseFullPage
中,通过page->child获取当前hotpage
的子page,并判断是否满了:
-- 如果满了则继续查找child-page,直到child-page不满,设置child-page为hotpage
,添加obj;
-- 或者child-page为空则重新生成一个新的page,new-page设置为hotPage
并添加obj - 如果hotpage没值,则调用
autoreleaseNoPage
,重新生成一个新的page,并设置为hotPage
,然后添加obj
这里要注意的是pushExtraBoundary
设置边界值也就是POOL_BOUNDARY
当时空page的时候设置nil边界。具体可看流程图
id *add(id obj)
{
printf("%p----\n", this);
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
objc_autorelease
id
objc_autorelease(id obj)
{
return AutoreleasePoolPage::autorelease(obj);
}
static inline id autorelease(id obj)
{
ASSERT(obj);
// ASSERT(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
printf("%p\n", dest);
return 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);
}
}
这里objc_autorelease
调用autorelease
,然后调用autoreleaseFast
,处理过程如第一部分的autoreleaseFast
一致
objc_autoreleasePoolPop
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
static inline void
pop(void *token)
{
//token为自动释放池中存储的要被释放的对象,当创建出autoreleasePool后如果没有存放数据token则为EMPTY_POOL_PLACEHOLDER的地址
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
//如果token存在,但是自动释放池中指向的是一个空池,(有可能自动释放池创建了出来,但是什么对象都没有存)
// Popping the top-level placeholder pool.
//则先取到当前hotpage
page = hotPage();
if (!page) {
//如果hotpage为空,则把当前线程所关联的page置为nil,然后返回(也就相当于pop了所有对象)
// 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.
//在hotpage有值的情况(pool被使用)下,查找hotpage的父节点(由于其父节点有可能为nil,所以递归查找其直属父节点),
page = coldPage();
//这里将指针指向该page的开始
token = page->begin();
} else {
//如果token不指向EMPTY_POOL_PLACEHOLDER,返回该page的首地址
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
//这里应该是增强一次判断,判断当前page是否指向开始,以及其父parent节点为空,上边的方法中做了这些处理,相当于清空当前page数据,否则执行badPop
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);
}
//最终执行popPage
return popPage<false>(token, page, stop);
}
pop
方法中主要的操作写到了注释中,这里需要注意的几个点:
pageForPointer 其实是根据token来获取token所在page的起始地址的方法
static AutoreleasePoolPage *pageForPointer(const void *p)
{
return pageForPointer((uintptr_t)p);
}
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
//该地址取余page的大小,得出该地址的偏移量相对于该page的偏移量
uintptr_t offset = p % SIZE;
ASSERT(offset >= sizeof(AutoreleasePoolPage));
//获取该page的起始地址;这里类似于比如说page每页size大小为10,然后传入的地址p为233,则该page的首地址为 233%10=3,然后233-3=230,也就是说该page的起始地址为230;
//因为page是以4K大小分页的,所以可以用上述方法
result = (AutoreleasePoolPage *)(p - offset);
result->fastcheck();
return result;
}
popPage循环删除page上存储的autorelease对象
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
//循环遍历删除page上存储的数据,并把autorelease对象置为SCRIBBLE
page->releaseUntil(stop);
// memory: delete empty children
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
} else 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();
}
}
}
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);
}
这里
page->releaseUntil(stop)
主要调用memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
方法来对page上存储的autorelease对象进行重置
重置之后,当前page调用kill方法,(kill方法官方解释为(free all of the pages)) 然后找到page->parent
,然后将page-parent
设置为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;
//首先找到当前page的最终的子孩子
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
//先用最终子孩子的父节点的child进行置nil,并进行delete关键字删除
//然后再继续往上找节点,先置nil child,然后删除该page
//直到删到传入的page为止
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
//
delete deathptr;
} while (deathptr != this);
}
其他知识
1.子线程在使用autorelease对象时,如果没有autoreleasepool会在autoreleaseNoPage中懒加载一个出来。
2.在runloop的run:beforeDate,以及一些source的callback中,有autoreleasepool的push和pop操作,总结就是系统在很多地方都差不多autorelease的管理操作。
3.就算插入没有pop也没关系,在线程exit的时候会释放资源,执行AutoreleasePoolPage::tls_dealloc,在这里面会清空autoreleasepool。
static void tls_dealloc(void *p)
{
if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
// No objects or pool pages to clean up here.
return;
}
// reinstate TLS value while we work
setHotPage((AutoreleasePoolPage *)p);
if (AutoreleasePoolPage *page = coldPage()) {
if (!page->empty()) objc_autoreleasePoolPop(page->begin()); // pop all of the pools
if (slowpath(DebugMissingPools || DebugPoolAllocation)) {
// pop() killed the pages already
} else {
page->kill(); // free all of the pages
}
}
// clear TLS value so TLS destruction doesn't loop
setHotPage(nil);
}