一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情。
自动释放池:AutoReleasePool
,是OC
中的一种内存自动回收机制,它可以将加入AutoreleasePool
中的变量release
的时机延迟。简单来说,当创建一个对象,在正常情况下,变量会在超出其作用域时立即release
。如果将其加入到自动释放池中,这个对象并不会立即释放,而会等到runloop
休眠/超出autoreleasepool
作用域之后进行释放。
Runloop
会处于休眠状态,等待用户交互来唤醒Runloop
- 用户每次交互都会启动一次
Runloop
,用于处理用户的所有点击、触摸等事件 Runloop
在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中- 在一次完整的
Runloop
结束之前,会向自动释放池中所有对象发送release
消息,然后销毁自动释放池
结构
使用cpp
文件探索
创建一个Mac
工程,在main.m
中,自动生成以下代码:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
生成cpp
文件
clang -rewrite-objc main.m -o main.cpp
打开cpp
文件,来到main
函数
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_da0d58_mi_0);
}
return 0;
}
autoreleasepool
被注释掉了,但作用域还在- 作用域中生成对
__AtAutoreleasePool
类型声明的代码 找到__AtAutoreleasePool
定义
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
__AtAutoreleasePool
是一个结构体,包含构造函数和析构函数- 结构体声明,触发构造函数,调用
objc_autoreleasePoolPush
函数 - 当结构体出作用域空间,触发析构函数,调用
objc_autoreleasePoolPop
函数
使用汇编代码探索
打开项目,在main
函数中autoreleasepool
处设置断点,查看汇编代码
- 调用
objc_autoreleasePoolPush
函数 - 调用
objc_autoreleasePoolPop
函数 进入objc_autoreleasePoolPush
函数
- 源码来自于
libobjc
框架
源码探索
打开objc4-818.2
源码,找到objc_autoreleasePoolPush
函数
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
- 调用
AutoreleasePoolPage
命名空间下的push
函数
AutoreleasePoolPage
找到AutoreleasePoolPage
的定义,首先看到这样一段注释:
/***********************************************************************
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.
每个指针要么是一个要释放的对象,要么是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的指针。当池被弹出,每个比哨兵热的对象都被释放
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
自动释放池边界,俗称:哨兵对象- 哨兵对象的作用:当自动释放池将对象进行
pop
操作时,需要知道边界在哪里,否则会破坏别人的内存空间。而哨兵对象,就是作为边界的标识而存在
- 哨兵对象的作用:当自动释放池将对象进行
- 自动释放池的栈空间被分成一个双链接结构的页面列表,可添加和删除页面
- 双向链表的特点,一个页中同时存在父节点和子节点。可向前找到父页面,也可向后找到子页面
- 线程本地存储指向热页,其中存储新自动释放的对象
- 栈原则,先进后出,可以理解为最后一个页面就是热页。里面的对象最后被
push
,最先被pop
AutoreleasePoolPage
继承于AutoreleasePoolPageData
- 栈原则,先进后出,可以理解为最后一个页面就是热页。里面的对象最后被
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:
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;
...
}
AutoreleasePoolPageData
找到AutoreleasePoolPageData
的定义
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)
{
}
};
结构体中,包含以下成员变量:
magic
:用来校验AutoreleasePoolPage
的结构是否完整next
:指向最新添加的autoreleased
对象的下一个位置,初始化时执行begin()
thread
:指向当前线parent
:指向父节点,第一个节点的parent
值为nil
child
:指向子节点,最后一个节点的child
值为nil
depth
:代表深度,从0
开始,往后递增1
hiwat
:代表high water mark
最大入栈数量标记
打印结构
搭建测试项目,关闭ARC
模式
打开main.m
文件,写入以下代码:
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *objc = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint();
}
return 0;
}
_objc_autoreleasePoolPrint
函数,用于打印自动释放池的结构- 创建NSObject实例对象,加入自动释放池
- 调用
_objc_autoreleasePoolPrint
函数,打印结构 输出以下内容:
##############
AUTORELEASE POOLS for thread 0x1000ebe00
2 releases pending.
[0x10700b000] ................ PAGE (hot) (cold)
[0x10700b038] ################ POOL 0x10700b038
[0x10700b040] 0x100705f60 NSObject
##############
- 打印出当前自动释放池所属线程
- 有
2
个需要释放的对象 - 当前的
Page
信息,占56字节
。因为只有一页,即是冷页面,也是热页面 - 哨兵对象
POOL
NSObject
对象
_objc_autoreleasePoolPrint
官方用于对自动释放池内容调试打印的函数
void
_objc_autoreleasePoolPrint(void)
{
AutoreleasePoolPage::printAll();
}
进入printAll
函数
static void printAll()
{
_objc_inform("##############");
_objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_self());
AutoreleasePoolPage *page;
ptrdiff_t objects = 0;
for (page = coldPage(); page; page = page->child) {
objects += page->next - page->begin();
}
_objc_inform("%llu releases pending.", (unsigned long long)objects);
if (haveEmptyPoolPlaceholder()) {
_objc_inform("[%p] ................ PAGE (placeholder)",
EMPTY_POOL_PLACEHOLDER);
_objc_inform("[%p] ################ POOL (placeholder)",
EMPTY_POOL_PLACEHOLDER);
}
else {
for (page = coldPage(); page; page = page->child) {
page->print();
}
}
_objc_inform("##############");
}
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
__attribute__((noinline, cold))
unsigned sumOfExtraReleases()
{
unsigned sumOfExtraReleases = 0;
for (id *p = begin(); p < next; p++) {
if (*p != POOL_BOUNDARY) {
sumOfExtraReleases += ((AutoreleasePoolEntry *)p)->count;
}
}
return sumOfExtraReleases;
}
#endif
__attribute__((noinline, cold))
static void printHiwat()
{
// Check and propagate high water mark
// Ignore high water marks under 256 to suppress noise.
AutoreleasePoolPage *p = hotPage();
uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin());
if (mark > p->hiwat + 256) {
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
unsigned sumOfExtraReleases = 0;
#endif
for( ; p; p = p->parent) {
p->unprotect();
p->hiwat = mark;
p->protect();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
sumOfExtraReleases += p->sumOfExtraReleases();
#endif
}
_objc_inform("POOL HIGHWATER: new high water mark of %u "
"pending releases for thread %p:",
mark, objc_thread_self());
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
if (sumOfExtraReleases > 0) {
_objc_inform("POOL HIGHWATER: extra sequential autoreleases of objects: %u",
sumOfExtraReleases);
}
#endif
void *stack[128];
int count = backtrace(stack, sizeof(stack)/sizeof(stack[0]));
char **sym = backtrace_symbols(stack, count);
for (int i = 0; i < count; i++) {
_objc_inform("POOL HIGHWATER: %s", sym[i]);
}
free(sym);
}
}
#undef POOL_BOUNDARY
};
- 按照自动释放池的结构,通过双向链表遍历
page
,依次读取page
中的内容并进行打印