内存自动回收机制
,它可以将加入AutoreleasePool
中的变量release
的时机延迟超出其作用域时
立即 release ,如果将其加入到自动释放池中,这个对象并不会立即释放,而会等到 runloop 休眠 / 超出autoreleasepool作用域之后
进行释放从程序启动到加载完成,主线程对应的 Runloop 会处于休眠状态,等待用户交互来唤醒 Runloop
用户每次交互都会启动一次 Runloop ,用于处理用户的所有点击、触摸等事件
Runloop 在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中
在一次完整的 Runloop 结束之前,会向自动释放池中所有对象发送 release 消息,然后销毁自动释放池
准备简单代码
#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
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; }
__AtAutoreleasePool
类型声明的代码struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} void * atautoreleasepoolobj; };
objc_autoreleasePoolPush
函数,在 结构体退出作用域析构 时调用objc_autoreleasePoolPop
函数,这两个函数也是下边研究的重点(在 main 函数中 autoreleasepool 处设置断点查看汇编也可以看到这两个函数的符号调用)ARC
模式// 导入 _objc_autoreleasePoolPrint 函数,用于打印自动释放池的结构 extern void _objc_autoreleasePoolPrint(void); int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *objc = [[[NSObject alloc] init] autorelease]; _objc_autoreleasePoolPrint(); } return 0; }
############## AUTORELEASE POOLS for thread 0x1000ebe00 2 releases pending. [0x10700b000] ................ PAGE (hot) (cold) [0x10700b038] ################ POOL 0x10700b038 [0x10700b040] 0x100705f60 NSObject ##############
_objc_autoreleasePoolPrint
调用 AutoreleasePoolPage::printAll()(通过AutoreleasePoolPage 的命名空间调用printAll()
);按照自动释放池的结构,通过双向链表遍历page
,依次读取 page 中的内容并进行打印哨兵对象:POOL
和 手动加入自动释放池 的对象objcPage
信息,占56字节
,因为只有一页,即是冷页面,也是热页面void * objc_autoreleasePoolPush(void) { // 调用 AutoreleasePoolPage 命名空间下的 push 函数 return AutoreleasePoolPage::push(); }
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. 线程本地存储指向热页,其中存储新自动释放的对象 **********************************************************************/
AutoreleasePoolPage 继承于AutoreleasePoolPageData
(有用的内容基本都在 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; ... }
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) { } };
结构体中,包含以下成员变量:(根据下边的成员大小得出 一个page占56字节)
magic
:用来校验 AutoreleasePoolPage 的结构是否完整(16字节)
next
:指向最新添加的autoreleased
对象的下一个位置,初始化时执行begin()
:获取对象压栈的起始位置(8字节)
thread
:指向当前线程(8字节)
parent
:指向父节点,第一个节点的parent
值为nil
(8字节)
child
:指向子节点,最后一个节点的child
值为nil
(8字节)
depth
:代表深度,从0
开始,往后递增1
(4字节)
hiwat
:代表high water mark
最大入栈数量标记(4字节)
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
:当自动释放池按顺序弹出时停止,并允许堆调试器跟踪自动释放池autoreleaseNewPage
函数,从一个新的池页开始创建autoreleaseFast
函数,将哨兵对象压栈autoreleaseFast
add
函数autoreleaseFullPage
函数遍历链表,找到最后一个空白的子页面
对其进行创建新页
设置为热页面
添加对象
autoreleaseNoPage
函数调用 AutoreleasePoolPage 构造函数,创建新页begin
:获取对象压栈的起始位置(sizeof(*this)
:大小取决于自身结构体中的成员变量、返回对象可压栈的真正开始地址,在成员变量以下)objc_thread_self
:通过tls
获取当前线程设置为热页面
pushExtraBoundary
为YES
,哨兵对象压栈
对象压栈
int main(int argc, const char * argv[]) { @autoreleasepool { for (int i = 0; i < 505; i++) { NSObject *objc = [[[NSObject alloc] init] autorelease]; } _objc_autoreleasePoolPrint(); } return 0; } ------------------------- objc[1804]: ############## objc[1804]: AUTORELEASE POOLS for thread 0x1000ebe00 objc[1804]: 506 releases pending. objc[1804]: [0x10200c000] ................ PAGE (full) (cold) objc[1804]: [0x10200c038] ################ POOL 0x10200c038 objc[1804]: [0x10200c040] 0x100638420 NSObject objc[1804]: [0x10200c048] 0x100637a40 NSObject objc[1804]: [0x10200c050] 0x100636970 NSObject ... objc[1804]: [0x100809000] ................ PAGE (hot) objc[1804]: [0x100809038] 0x10063a0b0 NSObject objc[1804]: ##############
504 * 8 = 4032
,加上56字节
成员变量和8字节
哨兵对象,共计4096
字节56字节
的成员变量void objc_autoreleasePoolPop(void *ctxt) { 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. //不存在热页面,将标记设置为nil return setHotPage(nil); } // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. //存在热页面,通过双向链表循环向上找到最冷页面 page = coldPage(); //将token设置为起始位置 token = page->begin(); } else { //获取token所在的页 page = pageForPointer(token); } //赋值给stop 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 //最冷页面的起始可能不是POOL_BOUNDARY: //1. 弹出顶级池,保留冷页面 //2. 对象在没有池的情况下被自动释放 } 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); }
static void popPage(void *token, AutoreleasePoolPage *page, id *stop) { if (allowDebug && PrintPoolHiwat) printHiwat(); //当前页中对象出栈,到stop位置停止 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 //特殊情况:删除所有的pop //销毁当前页面 page->kill(); //将热页面标记设置为nil 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 //向下遍历,到stop停止 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(); #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next; // create an obj with the zeroed out top byte and release that id obj = (id)entry->ptr; int count = (int)entry->count; // grab these before memset #else //内存平移,获取对象 id obj = *--page->next; #endif memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); //当前对象不是哨兵对象 if (obj != POOL_BOUNDARY) { #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS // release count+1 times since it is count of the additional // autoreleases beyond the first one for (int i = 0; i < count + 1; i++) { objc_release(obj); } #else //将其释放 objc_release(obj); #endif } } //将当前页面标记为热页面 setHotPage(this); #if DEBUG // we expect any children to be completely empty for (AutoreleasePoolPage *page = child; page; page = page->child) { ASSERT(page->empty()); } #endif }
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; //循环找到最后一个子页面 while (page->child) page = page->child; AutoreleasePoolPage *deathptr; do { deathptr = page; //找到父页面 page = page->parent; if (page) { //将子页面设置为nil page->unprotect(); page->child = nil; page->protect(); } //销毁子页面 delete deathptr; //遍历销毁到this为止 } while (deathptr != this); }
int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *objc = [[[NSObject alloc] init] autorelease]; @autoreleasepool { NSObject *objc = [[[NSObject alloc] init] autorelease]; } _objc_autoreleasePoolPrint(); } return 0; } ------------------------- objc[2511]: ############## objc[2511]: AUTORELEASE POOLS for thread 0x1000ebe00 objc[2511]: 4 releases pending. objc[2511]: [0x10680d000] ................ PAGE (hot) (cold) objc[2511]: [0x10680d038] ################ POOL 0x10680d038 objc[2511]: [0x10680d040] 0x101370c40 NSObject objc[2511]: [0x10680d048] ################ POOL 0x10680d048 objc[2511]: [0x10680d050] 0x101365fb0 NSObject objc[2511]: ##############
结构:
objc_autoreleasePoolPush
函数objc_autoreleasePoolPop
函数特点:
POOL_BOUNDARY
自动释放池边界,俗称:哨兵对象
pop
操作时,需要知道边界在哪里,否则会破坏别人的内存空间。而哨兵对象,就是边界标识push
,最先被pop
容量:
4096字节
,每一页都包含56字节
的成员变量,但一个自动释放池中,只会压栈一个哨兵对象,占8字节
原理:
自动释放池的本质是__AtAutoreleasePool
结构体,包含构造函数和析构函数
结构体声明,触发构造函数,调用objc_autoreleasePoolPush
函数,本质是对象压栈的push
方法
当结构体出作用域空间,触发析构函数,调用objc_autoreleasePoolPop
函数,本质是对象出栈的pop
方法
对象压栈
page
,并且没有存满,调用add
函数
*next++
进行内存平移page
,但存储已满,调用autoreleaseFullPage
函数page
,调用autoreleaseNoPage
函数AutoreleasePoolPageData
进行初始化begin
:获取对象压栈的起始位置objc_thread_self
:通过tls
获取当前线程pushExtraBoundary
为YES
,哨兵对象压栈对象出栈
popPage
函数,传入stop
为哨兵对象的位置stop
位置停止kill
函数,销毁当前页面嵌套使用:
ARC
模式:
ARC
模式,使用alloc
、new
、copy
、mutableCopy
前缀开头的方法进行对象创建,不会加入到自动释放池;它们的空间开辟由开发者申请,释放也由开发者进行管理与线程的关系:
autoreleased
对象被放置在当前线程的顶部自动释放池中;当一个线程终止时,它会自动清空所有与其关联的自动释放池与 Runloop 的关系:
使用:
for (int i = 0; i<100000000; i++) { @autoreleasepool { NSLog(@"%d",i); __autoreleasing LZPerson *p =[LZPerson new]; } }