自动释放池
oc中内存自动回收的一种机制,可以延长释放池中变量释放的时机,可以防止项目在运行过程中内存峰值过高。一般情况下创建的变量当超出其的作用域时,变量就会被释放。如果将变量加入到释放池,它的释放的时机就会被延迟。
哨兵对象
为nil,表示
autoreleasePool的内存边界,多个 @autoreleasePool 进行嵌套时,用于区分存储的数据是否属于当前内存释放池。一个自动释放池只存在一个 哨兵对象
autoreleasePool 运行流程
当第一次进入到 @autoreleasePool 中,objc_autoreleasePoolPush() 就会被调用,新建一个 AutoreleaseNewPage(), 然后会向其中添加一个 nil对象 作为哨兵对象,并返回该对象的地址。
当对象调用 autorelease 方法时,就会被添加到 AutoreleasePoolPage()中,若当前 page 不足以存放对象时,就会新建一个新的 AutoreleasePoolPage()
当要离开 @autoreleasePool 作用域时,objc_autoreleasePoolPop() 就会被调用,然后对page中的对象进行释放,一直到释放到哨兵对象为止
autoreleasepool() 方法结构
- objc_autoreleasePoolPush()
- push()
- autoreleaseNewPage()
- autoreleaseFullPage()
- autoreleaseNoPage()
- autoreleaseFast()
- page->add(obj)
- autoreleaseFullPage()
- autoreleaseNoPage()
- objc_autoreleasePoolPop()
- pop(ctxt)
- popPage()
- releaseUntil()
详细流程
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
可以看到,在使用了 @autoreleasepool{},在底层其实是调用了 objc_autoreleasePoolPush() 和 objc_autoreleasePoolPop() 方法
- objc_autoreleasePoolPush()
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
- AutoreleasePoolPage()
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
...
public:
//#define PAGE_MIN_SHIFT 12
//#define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)
static size_t const SIZE = PAGE_MIN_SIZE;
...
}
由上面代码可以知道
AutoreleasePoolPage是继承于AutoreleasePoolPageData。其一个page 大小是1<<12 = 4kb, 池中每个节点都是由AutoreleasePoolPage()组成,
struct AutoreleasePoolPageData
{
...
magic_t const magic; //校验 AutoreleasePoolPage 的结构是否完整 占位
16字节
__unsafe_unretained id *next; //指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() 8字节
pthread_t const thread; //指向当前线程 8字节
AutoreleasePoolPage * const parent; //指向父节点,第一个节点的 parent 为 nil 8字节
AutoreleasePoolPage *child; //指向子节点,最后一个节点的 child 为 nil 8字节
uint32_t const depth; //深度,从 0 开始,向后递增 1 4字节
uint32_t hiwat; //最大入栈数量标记 4字节
...
}
- push():若是首次进入调用 autoreleaseNewPag() 去添加哨兵对象,不是就调用 autoreleaseFast() 去添加哨兵对象
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page. 创建一个新的page
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
- autoreleaseNewPage():通过 hotPage() 寻找当前的 AutoreleasePoolPage 的节点,若 page 存在就插入 objc,若 page 不存在就新建一个 page
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
- hotPage():获取当前操作的 page
static inline AutoreleasePoolPage *hotPage()
{
//tls_get_direct:寻找线程的快速缓存
AutoreleasePoolPage *result = (AutoreleasePoolPage *)tls_get_direct(key);
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
//page 存在,返回page
if (result) result->fastcheck();
return result;
}
- 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.
//初始化第一页 page,AutoreleasePoolPage
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
//设置第一页page 为 hot page
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.
//向page里面插入 obj
return page->add(obj);
}
- 初始化 AutoreleasePoolPage()
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(), //计算数据插入的位置
objc_thread_self(), //当前线程
newParent, //当前父节点
newParent ? 1+newParent->depth : 0, //page中对象的深度
newParent ? newParent->hiwat : 0)
{
if (objc::PageCountWarning != -1) {
checkTooMuchAutorelease();
}
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
在向 AutoreleasePoolPage 添加数据的时候,他的起始位置是 当前的page的起始位置 加上 page大小 之后的位置开始。
AutoreleasePoolPageData 中的数据占用空间为 56 个字节,在向其中插入数据的位置将会从 第56个字节 开始
- autoreleaseFast():当page已经存在时调用
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) { //page 存在,并且 page 中空间未被站满
return page->add(obj);
} else if (page) { //page 存在,并且空间已经使用完了
return autoreleaseFullPage(obj, page);
} else { //page 不存在,就新建
return autoreleaseNoPage(obj);
}
}
- autoreleaseFullPage():去判断当前的 page 空间是否满了,如果满了,通过遍历去寻找 page 的子节点,如果没有找到子节点,就去新建一个 page,后面进行一个 插入数据的操作
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
...
do {
if (page->child) page = page->child; //遍历寻找子节点
else page = new AutoreleasePoolPage(page); //未找到子节点,就新建一个 page
} while (page->full());
setHotPage(page);
return page->add(obj);
}
__autoreleasing
@autoreleasepool {
__autoreleasing NSObject *obj = [NSObject alloc];
}
标记一个要被添加到自动释放池中的对象 由
alloc,new,copy,mutableCopy创建的对象,内存由ARC进行分配释放,不会被加入到自动释放池中,需要添加关键词__autoreleasing才会被加入到自动释放池中
通过汇编可以看到底层调用了 objc_autorelease
继续往下走,最终会调用
autoreleaseFast(id obj)
结尾补充
ARC下,autorelease对象什么时候释放?
分两种情况,如果是手动创建的autoreleasePool,则在释放池的作用域结束时候进行释放。
如果没有进行手动创建autoreleasePool,若是在主线程,就会在runloop即将休眠的时候,进行释放。若是在子线程,就会在子线程被销毁的时候,进行释放
func1() : func2()
func2() 给 func1()的类的成员变量进行赋值
autoreleasePool 和 runloop 的关系
当程序启动的时候,主线程的 runloop 会自动启动,并且会注册 runloop 的两个监听:
- 一个当runloop 将要被唤醒的时候,会自动创建 自动释放池,这个事件优先级最高,其目的是能让runloop中的事件能够加入到 autoreleasePool 中。
- 一个是当 runloop 将要进入到休眠的时候,会释放自动释放池,这个事件优先级最低,目的是让 runloop 里面的事件先于释放 autoreleasePool 这个事件完成。
- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。