autoreleasepool
参考:Using Autorelease Pool Blocks
Autorelease pool blocks provide a mechanism whereby you can relinquish ownership of an object, but avoid the possibility of it being deallocated immediately (such as when you return an object from a method).
翻译一下就是 Autorelease Pool 对管理的对象提供延迟释放机制。
形式
@autoreleasepool {
// Code that creates autoreleased objects.
}
//可以被嵌套
@autoreleasepool {
// . . .
@autoreleasepool {
// . . .
}
. . .
}
autoreleasepool 分析
@autoreleasepool
main.m
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
使用clang 改写成c++ 代码
clang -rewrite-objc main.m
目录下面会生成一个 main.cpp
, 拉到最下面
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_67_5cysdnrn6_dfz1lp4rknxkhh0000gp_T_main_0b8b3d_mi_0);
}
return 0;
}
看到autoreleasepool转换成了
{ __AtAutoreleasePool __autoreleasepool;
//...
}
找到__AtAutoreleasePool
的定义
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
__AtAutoreleasePool
是一个结构体, 构造函数调用objc_autoreleasePoolPush
析构的时候调用objc_autoreleasePoolPop
。
在{}
包裹的作用域里面创建的对象会被加入到该autoreleasepool
中管理,
作用域结束,__autoreleasepool
销毁,调用析构函数,对管理的对象发送release
消息。
相当于
{
//创建一个自动释放池
atautoreleasepoolobj = objc_autoreleasePoolPush();
//...添加对象到自动释放池
//弹出自动释放池,对其中的对象发送 release 消息
objc_autoreleasePoolPop(atautoreleasepoolobj) //
}
objc_autoreleasePoolPush
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
该方法在runtime源码中可以找到,位于NSObject.mm
中
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage 数据结构
结论: AutoreleasePoolPage 的结构是双向链表
b770ab80.png
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
static size_t const SIZE = 4096; //4KB
private:
//tls 对应的key
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const COUNT = SIZE / sizeof(id);
static size_t const MAX_FAULTS = 2;
// 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)
// auto release pool 哨兵
# define POOL_BOUNDARY nil
}
static成员不占用实例对象的内存空间,所以每个AutoreleasePoolPage实例的大小为56字节,
详见下面的分析。
AutoreleasePoolPage
是一个c++的类, 这个类继承自 AutoreleasePoolPageData
AutoreleasePoolPageData
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 的实例大小为 56 字节,AutoreleasePoolPage 类并没有添加新的成员变量,
因此实例的大小和父类的实例大小一致,都是 56 字节。
magic_t
struct magic_t {
static const uint32_t M0 = 0xA1A1A1A1;
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12;
uint32_t m[4];//16 bytes
magic_t() {
ASSERT(M1_len == strlen(M1));
ASSERT(M1_len == 3 * sizeof(m[1]));
m[0] = M0;
strncpy((char *)&m[1], M1, M1_len);
}
~magic_t() {
// Clear magic before deallocation.
// This prevents some false positives in memory debugging tools.
// fixme semantically this should be memset_s(), but the
// compiler doesn't optimize that at all (rdar://44856676).
volatile uint64_t *p = (volatile uint64_t *)m;
p[0] = 0; p[1] = 0;
}
bool check() const {
return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
}
bool fastcheck() const {
#if CHECK_AUTORELEASEPOOL
return check();
#else
return (m[0] == M0);
#endif
}
# undef M1
};
用于校验自动释放池是否被被损坏
- 构造成功
m[0]= 0xA1A1A1A1
m[1-3] = AUTORELEASE!
-
检查的时候
判断第一个字节是0xA1A1A1A1
, 后面三个字节是AUTORELEASE!
-
析构的时候
m[0] = 0
m[1-3] = 0
next
next 用于指向当前页的栈顶指针
thread
thread 保存了当前page所对应的线程
parent
当前节点的父节点
child
当前节点的子节点
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;
}
如果DebugPoolAllocation为true
执行autoreleaseNewPage
否则autoreleaseFast
slowpath(DebugPoolAllocation)
告诉编译器,预测该条件大概率为false
if (maybeMallocDebugging) {
const char *insert = getenv("DYLD_INSERT_LIBRARIES");
const char *zombie = getenv("NSZombiesEnabled");
const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
if ((getenv("MallocStackLogging")
|| getenv("MallocStackLoggingNoCompact")
|| (zombie && (*zombie == 'Y' || *zombie == 'y'))
|| (insert && strstr(insert, "libgmalloc")))
&&
(!pooldebug || 0 == strcmp(pooldebug, "YES")))
{
DebugPoolAllocation = true;
}
}
如果是debug内存问题,开启以上选项的时候,每次都将创建新的page
大多数情况下执行的都是autoreleaseFast
autoreleaseNewPage
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
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;
}
hotPage 获取当前正在使用的page。从当前线程的tls(thread local storage)中获取
key为static pthread_key_t const key = AUTORELEASE_POOL_KEY;
的值,对应的就是当前的hotpage,可能为空。
关于 EMPTY_POOL_PLACEHOLDER
// 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)
验证
#import <Foundation/Foundation.h>
#import <pthread.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
pthread_key_t key = 43;
uint64_t val = (uint64_t)pthread_getspecific(key);
NSLog(@"%llu",val);
dispatch_group_leave(group);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}
return 0;
}
输出 val = 1
, 而EMPTY_POOL_PLACEHOLDER
刚好等于1.
分析: gcd启动了新的线程,push了一个占位的autoreleasepool,在去添加对象的时候,才会创建真正的自动释放池page。
如果没有添加对象,创建了自动释放池page(4KB),会造成内存浪费。这是一个优化点。
为什么key是43 ?
objc-os.h
#if defined(__PTK_FRAMEWORK_OBJC_KEY0)
# define SUPPORT_DIRECT_THREAD_KEYS 1
# define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
# define SYNC_DATA_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1)
# define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2)
# define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
# if SUPPORT_RETURN_AUTORELEASE
# define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4)
# endif
#else
# define SUPPORT_DIRECT_THREAD_KEYS 0
#endif
因此 AUTORELEASE_POOL_KEY
实际对应 __PTK_FRAMEWORK_OBJC_KEY3
而__PTK_FRAMEWORK_OBJC_KEY3
/* Keys 40-49 for Objective-C runtime usage */
#define __PTK_FRAMEWORK_OBJC_KEY0 40
#define __PTK_FRAMEWORK_OBJC_KEY1 41
#define __PTK_FRAMEWORK_OBJC_KEY2 42
#define __PTK_FRAMEWORK_OBJC_KEY3 43
#define __PTK_FRAMEWORK_OBJC_KEY4 44
#define __PTK_FRAMEWORK_OBJC_KEY5 45
#define __PTK_FRAMEWORK_OBJC_KEY6 46
#define __PTK_FRAMEWORK_OBJC_KEY7 47
#define __PTK_FRAMEWORK_OBJC_KEY8 48
#define __PTK_FRAMEWORK_OBJC_KEY9 49
autoreleaseFast
static inline id *autoreleaseFast(id obj)
{
//如果obj 是nil,会添加一个哨兵
//obj不为nil,添加对象到自动释放池
//取得当前hotpage
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
//存在,并且没有满,添加obj到hotpage
return page->add(obj);
} else if (page) {
//存在,已满,创建新的page,父节点为当前page
return autoreleaseFullPage(obj, page);
} else {
//不存在,创建page,设置为hotpage,添加obj到创建的page
return autoreleaseNoPage(obj);
}
}
autoreleaseFullPage
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);
/*
以当前page为父节点,顺着子节点往下,找到第一个还未满的的page
找不到,创建新的page
设置hotpage
添加obj到hotpage
*/
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
autoreleaseNoPage
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
/*
没有page 代表
1. 没有pool被push, hotpage == nil
2. 有一个占位的pool被push,里面没有任何内容
*/
ASSERT(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
/*
tls对应key为AUTORELEASE_POOL_KEY的value为1
此时创建page,需要在page中添加哨兵对象
*/
// 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) {
/*
没有hotpage, 需要创建page, 添加的第一个obj应该是哨兵对象
否则就出错了
*/
// 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) {
/*
没有hotpage, 没有占位pool page
此时添加哨兵,直接在tls里保存一个占位的poolpage
等待添加对象的时候,再去真正创建pool page
*/
// 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.
/*
hotpage 不存在,创建第一个page,父节点是nil
设置为hotpage
将obj添加到该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 创建
构造函数
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
{
if (objc::PageCountWarning != -1) {
checkTooMuchAutorelease();
}
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
父类的构造
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)
{
}
关键参数:
- 父节点(双向链表,指向父节点的指针)
- next指针(向page中添加对象,指向下一个可以添加的位置)
- pthread_t 当前线程id
- depth 和 hiwat 暂时不讨论
AutoreleasePoolPage 内存分配
//SIZE = 4096 = 4KB
static void * operator new(size_t size) {
return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
}
static void operator delete(void * p) {
return free(p);
}
AutoreleasePoolPage 重载了new运算符,为实例分配了4KB的内存。
其中 56Byte是AutoreleasePoolPage实例的大小,剩下的4040Byte用来存储被管理的对象。
被管理的对象的类型是id*
, 64位下占8Byte, 因此一个page最多可以存储505个对象。
//添加对象起始地址
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
//添加对象结束地址
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
//空page,未管理任何对象
bool empty() {
return next == begin();
}
//page已满, next == end
bool full() {
return next == end();
}
//不满一半
bool lessThanHalfFull() {
return (next - begin() < (end() - begin()) / 2);
}
AutoreleasePoolPage 存在于堆区,前面56个字节存当前实例this,后面4040个字节存管理的对象。
761bbd9f.png
begin指向于56字节处, end 指向于 4096字节。
next指向下一个空闲的位置,当next = end,表示page满了。
在一次push中
- 首先插入哨兵对象,将next++
- 将对象添加到next位置,next++
- 如果当前页满了,创建新page,继续添加对象。
- 因此会产生跨页的自动释放池。
static inline id autorelease(id obj)
{
ASSERT(!obj->isTaggedPointerOrNil());
//调用autoreleaseFast添加对象
id *dest __unused = autoreleaseFast(obj);
return obj;
}
因此添加相当于
atautoreleasepoolobj = objc_autoreleasePoolPush();
//添加对象到自动释放池
autorelease(obj);
autorelease(obj);
autorelease(obj);
...
//弹出自动释放池,对其中的对象发送 release 消息
objc_autoreleasePoolPop(atautoreleasepoolobj)
add
id *add(id obj)
{
id *ret = next;
*next++ = obj;
return ret;
}
将obj保存在next指向的位置,next++。
返回obj存储的地址。
因此
atautoreleasepoolobj = objc_autoreleasePoolPush();
objc_autoreleasePoolPop(atautoreleasepoolobj)
atautoreleasepoolobj 就是当前自动释放池push进的哨兵对象所指的位置,
当pop的时候,遇到该哨兵对象就停止出栈。
AutoreleasePoolPage::pop(ctxt)
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 对应的 page
- 调用popPage方法
static inline void pop(void *token)
{
AutoreleasePoolPage *page = pageForPointer(token);
return popPage<false>(token, page, stop);
}
pageForPointer
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
//哨兵地址于page基地址的偏移
uintptr_t offset = p % SIZE;
ASSERT(offset >= sizeof(AutoreleasePoolPage));
//得到page的基地址
result = (AutoreleasePoolPage *)(p - offset);
//检查magic字段,判断page是否被破坏
result->fastcheck();
//返回找到的page
return result;
}
指针p 指向于 pop 时的哨兵对象。 pop 时,哨兵所在的page地址。
SIZE = 4096
通过计算uintptr_t offset = p % SIZE
得到哨兵基于page基地址的偏移
用p - offset
得到page基地址。
可以直接求余的原因是每个page都是4096大小,以4096在内存中对齐。
popPage
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
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();
}
}
}
简化一下
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销毁
page->child->kill();
}
else if (page->child->child) {
//否则将page->child->child销毁掉,保留一个子节点
page->child->child->kill();
}
}
}
releaseUntil 方法分析
void releaseUntil(id *stop)
{
/*
存在跨页面的自动释放,从链表的尾部开始出栈,一直到当前页面
遇到stop标记的时候停止
*/
while (this->next != stop) {
//找到hotpage,跨页面的话就是链表的尾部
AutoreleasePoolPage *page = hotPage();
// 找到第一个不为空的page,设置为hotpage
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
//保存出栈对象
id obj = *--page->next;
//page->next指向的内存改写为0xA3A3A3A3
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
if (obj != POOL_BOUNDARY) {
//给obj发送release消息
objc_release(obj);
}
}
//当前页面出栈到stop标志位,设置当前页为hotpage
setHotPage(this);
}
kill 方法分析
void kill()
{
//找到链表的末尾
AutoreleasePoolPage *page = this;
while (page->child) page = page->child;
//从链表末尾开始删除节点
//do..while,可能删除的是当前节点
AutoreleasePoolPage *deathptr;
do {
//找到子节点
deathptr = page;
//找到子节点的父节点
page = page->parent;
if (page) {
//将父节点的child 置空
page->child = nil;
}
//销毁子节点
delete deathptr;
} while (deathptr != this);
}
参考:
自动释放池的前世今生 ---- 深入解析 autoreleasepool
Using Autorelease Pool Blocks
网友评论