1. 概述

AutoReleasePool:自动释放池

OC中的一种内存自动回收机制,它可以将加入AutoreleasePool中的变量release的时机延迟

简单来说,当创建一个对象,在正常情况下,变量会在超出其作用域时立即release。如果将其加入到自动释放池中,这个对象并不会立即释放,而会等到runloop休眠/超出autoreleasepool作用域之后进行释放
image.png

  • 从程序启动到加载完成,主线程对应的Runloop会处于休眠状态,等待用户交互来唤醒Runloop

  • 用户每次交互都会启动一次Runloop,用于处理用户的所有点击、触摸等事件

  • Runloop在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中

  • 在一次完整的Runloop结束之前,会向自动释放池中所有对象发送release消息,然后销毁自动释放池

2. 结构

2.1 使用cpp文件探索

创建一个Mac工程,在main.m中,自动生成以下代码:

  1. #import <Foundation/Foundation.h>
  2. int main(int argc, const char * argv[]) {
  3. @autoreleasepool {
  4. NSLog(@"Hello, World!");
  5. }
  6. return 0;
  7. }

生成cpp文件

  1. clang -rewrite-objc main.m -o main.cpp

打开cpp文件,来到main函数

  1. int main(int argc, const char * argv[]) {
  2. /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  3. NSLog((NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_da0d58_mi_0);
  4. }
  5. return 0;
  6. }
  • autoreleasepool被注释掉了,但作用域还在
  • 作用域中生成对__AtAutoreleasePool类型声明的代码

找到__AtAutoreleasePool定义

  1. struct __AtAutoreleasePool {
  2. __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  3. ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  4. void * atautoreleasepoolobj;
  5. };
  • __AtAutoreleasePool是一个结构体,包含构造函数和析构函数
  • 结构体声明,触发构造函数,调用objc_autoreleasePoolPush函数
  • 当结构体出作用域空间,触发析构函数,调用objc_autoreleasePoolPop函数

2.2 使用汇编代码探索

打开项目,在main函数中autoreleasepool处设置断点,查看汇编代码:
image.png

  • 调用objc_autoreleasePoolPush函数
  • 调用objc_autoreleasePoolPop函数

进入objc_autoreleasePoolPush函数
image.png

  • 源码来自于libobjc框架

2.3 源码探索

打开objc4-818.2源码,找到objc_autoreleasePoolPush函数

  1. void *
  2. objc_autoreleasePoolPush(void)
  3. {
  4. return AutoreleasePoolPage::push();
  5. }
  • 调用AutoreleasePoolPage命名空间下的push函数

2.3.1 AutoreleasePoolPage

找到AutoreleasePoolPage的定义,首先看到这样一段注释:

  1. /***********************************************************************
  2. Autorelease pool implementation
  3. A thread's autorelease pool is a stack of pointers.
  4. 线程的自动释放池是一个指针堆栈
  5. Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
  6. 每个指针要么是一个要释放的对象,要么是POOL_BOUNDARY自动释放池边界
  7. 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.
  8. 池令牌是指向该池的POOL_BOUNDARY的指针。当池被弹出,每个比哨兵热的对象都被释放
  9. The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.
  10. 堆栈被分成一个双链接的页面列表。根据需要添加和删除页面
  11. Thread-local storage points to the hot page, where newly autoreleased objects are stored.
  12. 线程本地存储指向热页,其中存储新自动释放的对象
  13. **********************************************************************/

通过注释我们可以了解到以下几点:

  • 自动释放池和线程有关系

  • 自动释放池是一个存储指针的栈结构

  • 指针要么是一个要释放的对象,要么是POOL_BOUNDARY自动释放池边界,俗称:哨兵对象

    • 哨兵对象的作用:当自动释放池将对象进行pop操作时,需要知道边界在哪里,否则会破坏别人的内存空间。而哨兵对象,就是作为边界的标识而存在
  • 自动释放池的栈空间被分成一个双链接结构的页面列表,可添加和删除页面

    • 双向链表的特点,一个页中同时存在父节点和子节点。可向前找到父页面,也可向后找到子页面
  • 线程本地存储指向热页,其中存储新自动释放的对象

    • 栈原则,先进后出,可以理解为最后一个页面就是热页。里面的对象最后被push,最先被pop

AutoreleasePoolPage继承于AutoreleasePoolPageData

  1. class AutoreleasePoolPage : private AutoreleasePoolPageData
  2. {
  3. friend struct thread_data_t;
  4. public:
  5. static size_t const SIZE =
  6. #if PROTECT_AUTORELEASEPOOL
  7. PAGE_MAX_SIZE; // must be multiple of vm page size
  8. #else
  9. PAGE_MIN_SIZE; // size and alignment, power of 2
  10. #endif
  11. private:
  12. static pthread_key_t const key = AUTORELEASE_POOL_KEY;
  13. static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
  14. static size_t const COUNT = SIZE / sizeof(id);
  15. static size_t const MAX_FAULTS = 2;
  16. ...
  17. }

2.3.2 AutoreleasePoolPageData

找到AutoreleasePoolPageData的定义

  1. class AutoreleasePoolPage;
  2. struct AutoreleasePoolPageData
  3. {
  4. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  5. struct AutoreleasePoolEntry {
  6. uintptr_t ptr: 48;
  7. uintptr_t count: 16;
  8. static const uintptr_t maxCount = 65535; // 2^16 - 1
  9. };
  10. static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
  11. #endif
  12. magic_t const magic;
  13. __unsafe_unretained id *next;
  14. pthread_t const thread;
  15. AutoreleasePoolPage * const parent;
  16. AutoreleasePoolPage *child;
  17. uint32_t const depth;
  18. uint32_t hiwat;
  19. AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
  20. : magic(), next(_next), thread(_thread),
  21. parent(_parent), child(nil),
  22. depth(_depth), hiwat(_hiwat)
  23. {
  24. }
  25. };

结构体中,包含以下成员变量:

  • magic:用来校验AutoreleasePoolPage的结构是否完整

  • next:指向最新添加的autoreleased对象的下一个位置,初始化时执行begin()

  • thread:指向当前线程

  • parent:指向父节点,第一个节点的parent值为nil

  • child:指向子节点,最后一个节点的child值为nil

  • depth:代表深度,从0开始,往后递增1

  • hiwat:代表high water mark最大入栈数量标记

2.3.3 打印结构

搭建测试项目,关闭ARC模式
image.png

打开main.m文件,写入以下代码:

  1. extern void _objc_autoreleasePoolPrint(void);
  2. int main(int argc, const char * argv[]) {
  3. @autoreleasepool {
  4. NSObject *objc = [[[NSObject alloc] init] autorelease];
  5. _objc_autoreleasePoolPrint();
  6. }
  7. return 0;
  8. }
  • 导入_objc_autoreleasePoolPrint函数,用于打印自动释放池的结构
  • 创建NSObject实例对象,加入自动释放池
  • 调用_objc_autoreleasePoolPrint函数,打印结构

输出以下内容:

  1. ##############
  2. AUTORELEASE POOLS for thread 0x1000ebe00
  3. 2 releases pending.
  4. [0x10700b000] ................ PAGE (hot) (cold)
  5. [0x10700b038] ################ POOL 0x10700b038
  6. [0x10700b040] 0x100705f60 NSObject
  7. ##############
  • 打印出当前自动释放池所属线程

  • 2个需要释放的对象

  • 当前的Page信息,占56字节。因为只有一页,即是冷页面,也是热页面

  • 哨兵对象POOL

  • NSObject对象

2.3.4 _objc_autoreleasePoolPrint

官方用于对自动释放池内容调试打印的函数

  1. void
  2. _objc_autoreleasePoolPrint(void)
  3. {
  4. AutoreleasePoolPage::printAll();
  5. }

进入printAll函数

  1. static void printAll()
  2. {
  3. _objc_inform("##############");
  4. _objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_self());
  5. AutoreleasePoolPage *page;
  6. ptrdiff_t objects = 0;
  7. for (page = coldPage(); page; page = page->child) {
  8. objects += page->next - page->begin();
  9. }
  10. _objc_inform("%llu releases pending.", (unsigned long long)objects);
  11. if (haveEmptyPoolPlaceholder()) {
  12. _objc_inform("[%p] ................ PAGE (placeholder)",
  13. EMPTY_POOL_PLACEHOLDER);
  14. _objc_inform("[%p] ################ POOL (placeholder)",
  15. EMPTY_POOL_PLACEHOLDER);
  16. }
  17. else {
  18. for (page = coldPage(); page; page = page->child) {
  19. page->print();
  20. }
  21. }
  22. _objc_inform("##############");
  23. }
  24. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  25. __attribute__((noinline, cold))
  26. unsigned sumOfExtraReleases()
  27. {
  28. unsigned sumOfExtraReleases = 0;
  29. for (id *p = begin(); p < next; p++) {
  30. if (*p != POOL_BOUNDARY) {
  31. sumOfExtraReleases += ((AutoreleasePoolEntry *)p)->count;
  32. }
  33. }
  34. return sumOfExtraReleases;
  35. }
  36. #endif
  37. __attribute__((noinline, cold))
  38. static void printHiwat()
  39. {
  40. // Check and propagate high water mark
  41. // Ignore high water marks under 256 to suppress noise.
  42. AutoreleasePoolPage *p = hotPage();
  43. uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin());
  44. if (mark > p->hiwat + 256) {
  45. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  46. unsigned sumOfExtraReleases = 0;
  47. #endif
  48. for( ; p; p = p->parent) {
  49. p->unprotect();
  50. p->hiwat = mark;
  51. p->protect();
  52. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  53. sumOfExtraReleases += p->sumOfExtraReleases();
  54. #endif
  55. }
  56. _objc_inform("POOL HIGHWATER: new high water mark of %u "
  57. "pending releases for thread %p:",
  58. mark, objc_thread_self());
  59. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  60. if (sumOfExtraReleases > 0) {
  61. _objc_inform("POOL HIGHWATER: extra sequential autoreleases of objects: %u",
  62. sumOfExtraReleases);
  63. }
  64. #endif
  65. void *stack[128];
  66. int count = backtrace(stack, sizeof(stack)/sizeof(stack[0]));
  67. char **sym = backtrace_symbols(stack, count);
  68. for (int i = 0; i < count; i++) {
  69. _objc_inform("POOL HIGHWATER: %s", sym[i]);
  70. }
  71. free(sym);
  72. }
  73. }
  74. #undef POOL_BOUNDARY
  75. };
  • 按照自动释放池的结构,通过双向链表遍历page,依次读取page中的内容并进行打印

3. 对象压栈

进入objc_autoreleasePoolPush函数

  1. void *
  2. objc_autoreleasePoolPush(void)
  3. {
  4. return AutoreleasePoolPage::push();
  5. }

进入AutoreleasePoolPage命名空间下的push函数

  1. static inline void *push()
  2. {
  3. id *dest;
  4. if (slowpath(DebugPoolAllocation)) {
  5. // Each autorelease pool starts on a new pool page.
  6. dest = autoreleaseNewPage(POOL_BOUNDARY);
  7. } else {
  8. dest = autoreleaseFast(POOL_BOUNDARY);
  9. }
  10. ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
  11. return dest;
  12. }
  • DebugPoolAllocation:当自动释放池按顺序弹出时停止,并允许堆调试器跟踪自动释放池
  • 不存在,调用autoreleaseNewPage函数,从一个新的池页开始创建
  • 否则,调用autoreleaseFast函数,将哨兵对象压栈

3.1 autoreleaseFast

进入autoreleaseFast函数

  1. static inline id *autoreleaseFast(id obj)
  2. {
  3. AutoreleasePoolPage *page = hotPage();
  4. if (page && !page->full()) {
  5. return page->add(obj);
  6. } else if (page) {
  7. return autoreleaseFullPage(obj, page);
  8. } else {
  9. return autoreleaseNoPage(obj);
  10. }
  11. }
  • 如果存在page,并且没有存满,调用add函数

  • 如果存在page,但存储已满,调用autoreleaseFullPage函数

  • 否则,不存在page,调用autoreleaseNoPage函数

3.1.1 autoreleaseNoPage

进入autoreleaseNoPage函数

  1. static __attribute__((noinline))
  2. id *autoreleaseNoPage(id obj)
  3. {
  4. // "No page" could mean no pool has been pushed
  5. // or an empty placeholder pool has been pushed and has no contents yet
  6. ASSERT(!hotPage());
  7. bool pushExtraBoundary = false;
  8. ...
  9. // We are pushing an object or a non-placeholder'd pool.
  10. // Install the first page.
  11. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
  12. setHotPage(page);
  13. // Push a boundary on behalf of the previously-placeholder'd pool.
  14. if (pushExtraBoundary) {
  15. page->add(POOL_BOUNDARY);
  16. }
  17. // Push the requested object or pool.
  18. return page->add(obj);
  19. }
  • 调用AutoreleasePoolPage构造函数,创建新页
  • 设置为热页面
  • pushExtraBoundaryYES,哨兵对象压栈
  • 对象压栈

进入AutoreleasePoolPage构造函数

  1. AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
  2. AutoreleasePoolPageData(begin(),
  3. objc_thread_self(),
  4. newParent,
  5. newParent ? 1+newParent->depth : 0,
  6. newParent ? newParent->hiwat : 0)
  7. {
  8. if (objc::PageCountWarning != -1) {
  9. checkTooMuchAutorelease();
  10. }
  11. if (parent) {
  12. parent->check();
  13. ASSERT(!parent->child);
  14. parent->unprotect();
  15. parent->child = this;
  16. parent->protect();
  17. }
  18. protect();
  19. }
  • 通过父类AutoreleasePoolPageData进行初始化
  • begin:获取对象压栈的起始位置
  • objc_thread_self:通过tls获取当前线程
  • 链接双向链表

进入begin函数

  1. id * begin() {
  2. return (id *) ((uint8_t *)this+sizeof(*this));
  3. }
  • sizeof(*this):大小取决于自身结构体中的成员变量
  • 返回对象可压栈的真正开始地址,在成员变量以下

进入AutoreleasePoolPageData的定义

  1. struct AutoreleasePoolPageData
  2. {
  3. ...
  4. magic_t const magic;
  5. __unsafe_unretained id *next;
  6. pthread_t const thread;
  7. AutoreleasePoolPage * const parent;
  8. AutoreleasePoolPage *child;
  9. uint32_t const depth;
  10. uint32_t hiwat;
  11. ...
  12. };
  • magic16字节
  • nextthreadparentchild:各占8字节
  • depthhiwat:各占4字节
  • 共占56字节

进入magic_t的定义

  1. struct magic_t {
  2. static const uint32_t M0 = 0xA1A1A1A1;
  3. # define M1 "AUTORELEASE!"
  4. static const size_t M1_len = 12;
  5. uint32_t m[4];
  6. ...
  7. # undef M1
  8. };
  • 结构体的创建在堆区申请内存,而静态成员存储在静态区,不占结构体大小
  • 16字节来自于uint32_t数组

进入objc_thread_self函数

  1. static inline pthread_t objc_thread_self()
  2. {
  3. return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
  4. }
  • 通过tls获取当前线程

3.1.2 autoreleaseFullPage

进入autoreleaseFullPage函数

  1. id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
  2. {
  3. // The hot page is full.
  4. // Step to the next non-full page, adding a new page if necessary.
  5. // Then add the object to that page.
  6. ASSERT(page == hotPage());
  7. ASSERT(page->full() || DebugPoolAllocation);
  8. do {
  9. if (page->child) page = page->child;
  10. else page = new AutoreleasePoolPage(page);
  11. } while (page->full());
  12. setHotPage(page);
  13. return page->add(obj);
  14. }
  • 遍历链表,找到最后一个空白的子页面
  • 对其进行创建新页
  • 设置为热页面
  • 添加对象

形成以下数据结构:
image.png

3.1.3 add

进入add函数

  1. id *add(id obj)
  2. {
  3. ASSERT(!full());
  4. unprotect();
  5. id *ret;
  6. ...
  7. ret = next; // faster than `return next-1` because of aliasing
  8. *next++ = obj;
  9. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  10. // Make sure obj fits in the bits available for it
  11. ASSERT(((AutoreleasePoolEntry *)ret)->ptr == (uintptr_t)obj);
  12. #endif
  13. done:
  14. protect();
  15. return ret;
  16. }
  • 使用*next++进行内存平移
  • 将对象压栈

3.2 autoreleaseNewPage

进入autoreleaseNewPage函数

  1. id *autoreleaseNewPage(id obj)
  2. {
  3. AutoreleasePoolPage *page = hotPage();
  4. if (page) return autoreleaseFullPage(obj, page);
  5. else return autoreleaseNoPage(obj);
  6. }
  • 获取热页面
  • 存在,调用autoreleaseFullPage函数
  • 否则,不存在page,调用autoreleaseNoPage函数

4. autorelease

进入objc_autorelease函数

  1. id
  2. objc_autorelease(id obj)
  3. {
  4. if (obj->isTaggedPointerOrNil()) return obj;
  5. return obj->autorelease();
  6. }
  • 当前对象为TaggedPointernil,直接返回
  • 否则,调用对象的autorelease函数

进入autorelease函数

  1. inline id
  2. objc_object::autorelease()
  3. {
  4. ASSERT(!isTaggedPointer());
  5. if (fastpath(!ISA()->hasCustomRR())) {
  6. return rootAutorelease();
  7. }
  8. return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
  9. }
  • 如果是自定义类,调用rootAutorelease函数

进入rootAutoreleaserootAutorelease2AutoreleasePoolPage::autorelease函数

  1. inline id
  2. objc_object::rootAutorelease()
  3. {
  4. if (isTaggedPointer()) return (id)this;
  5. if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
  6. return rootAutorelease2();
  7. }
  8. id
  9. objc_object::rootAutorelease2()
  10. {
  11. ASSERT(!isTaggedPointer());
  12. return AutoreleasePoolPage::autorelease((id)this);
  13. }
  14. static inline id autorelease(id obj)
  15. {
  16. ASSERT(!obj->isTaggedPointerOrNil());
  17. id *dest __unused = autoreleaseFast(obj);
  18. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  19. ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
  20. #else
  21. ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
  22. #endif
  23. return obj;
  24. }
  • 调用autoreleaseFast函数,对象压栈

5. 池页容量

自动释放池采用分页的方式存储对象,因为对象在频繁压栈和出栈的过程中,产生异常,值会影响当前页面,不会影响到整个自动释放池

并且自动释放池分页管理,每页之前的地址可以不连续,它们可以使用双向链表找到父页面和子页面。如果所有对象都使用一页存储,为了保证地址的连续性,每次扩容会相对繁琐和耗时

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. for (int i = 0; i < 505; i++) {
  4. NSObject *objc = [[[NSObject alloc] init] autorelease];
  5. }
  6. _objc_autoreleasePoolPrint();
  7. }
  8. return 0;
  9. }
  10. -------------------------
  11. objc[1804]: ##############
  12. objc[1804]: AUTORELEASE POOLS for thread 0x1000ebe00
  13. objc[1804]: 506 releases pending.
  14. objc[1804]: [0x10200c000] ................ PAGE (full) (cold)
  15. objc[1804]: [0x10200c038] ################ POOL 0x10200c038
  16. objc[1804]: [0x10200c040] 0x100638420 NSObject
  17. objc[1804]: [0x10200c048] 0x100637a40 NSObject
  18. objc[1804]: [0x10200c050] 0x100636970 NSObject
  19. ...
  20. objc[1804]: [0x100809000] ................ PAGE (hot)
  21. objc[1804]: [0x100809038] 0x10063a0b0 NSObject
  22. objc[1804]: ##############
  • 505NSObject对象循环加入自动释放池,当存储504个对象时,池页已满。第505个对象创建新池页存储

  • 一页的容量:504 * 8 = 4032,加上56字节成员变量和8字节哨兵对象,共计4096字节

  • 每一页都存在56字节的成员变量

  • 一个自动释放池,只会压栈一个哨兵对象

在源码中查看

  1. class AutoreleasePoolPage : private AutoreleasePoolPageData
  2. {
  3. friend struct thread_data_t;
  4. public:
  5. static size_t const SIZE =
  6. #if PROTECT_AUTORELEASEPOOL
  7. PAGE_MAX_SIZE; // must be multiple of vm page size
  8. #else
  9. PAGE_MIN_SIZE; // size and alignment, power of 2
  10. #endif
  11. ...
  12. }

来到PAGE_MIN_SIZE的定义

  1. #define PAGE_MIN_SHIFT 12
  2. #define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)
  • 1左移12位,相当于2 ^ 12 = 4096

6. 对象出栈

进入objc_autoreleasePoolPop函数

  1. void
  2. objc_autoreleasePoolPop(void *ctxt)
  3. {
  4. AutoreleasePoolPage::pop(ctxt);
  5. }

进入pop函数

  1. static inline void
  2. pop(void *token)
  3. {
  4. AutoreleasePoolPage *page;
  5. id *stop;
  6. //判断当前对象是否为空占位符
  7. if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
  8. // Popping the top-level placeholder pool.
  9. //获取热页面
  10. page = hotPage();
  11. if (!page) {
  12. // Pool was never used. Clear the placeholder.
  13. //不存在热页面,将标记设置为nil
  14. return setHotPage(nil);
  15. }
  16. // Pool was used. Pop its contents normally.
  17. // Pool pages remain allocated for re-use as usual.
  18. //存在热页面,通过双向链表循环向上找到最冷页面
  19. page = coldPage();
  20. //将token设置为起始位置
  21. token = page->begin();
  22. } else {
  23. //获取token所在的页
  24. page = pageForPointer(token);
  25. }
  26. //赋值给stop
  27. stop = (id *)token;
  28. //当前位置不是哨兵对象
  29. if (*stop != POOL_BOUNDARY) {
  30. if (stop == page->begin() && !page->parent) {
  31. // Start of coldest page may correctly not be POOL_BOUNDARY:
  32. // 1. top-level pool is popped, leaving the cold page in place
  33. // 2. an object is autoreleased with no pool
  34. //最冷页面的起始可能不是POOL_BOUNDARY:
  35. //1. 弹出顶级池,保留冷页面
  36. //2. 对象在没有池的情况下被自动释放
  37. } else {
  38. // Error. For bincompat purposes this is not
  39. // fatal in executables built with old SDKs.
  40. //出现异常情况
  41. return badPop(token);
  42. }
  43. }
  44. if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
  45. return popPageDebug(token, page, stop);
  46. }
  47. //出栈
  48. return popPage<false>(token, page, stop);
  49. }

6.1 popPage

进入popPage函数

  1. static void
  2. popPage(void *token, AutoreleasePoolPage *page, id *stop)
  3. {
  4. if (allowDebug && PrintPoolHiwat) printHiwat();
  5. //当前页中对象出栈,到stop位置停止
  6. page->releaseUntil(stop);
  7. // memory: delete empty children
  8. if (allowDebug && DebugPoolAllocation && page->empty()) {
  9. // special case: delete everything during page-per-pool debugging
  10. //特殊情况:在逐页池调试期间删除所有内容
  11. //获取父页面
  12. AutoreleasePoolPage *parent = page->parent;
  13. //销毁当前页面
  14. page->kill();
  15. //将父页面设置为热页面
  16. setHotPage(parent);
  17. } else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
  18. // special case: delete everything for pop(top)
  19. // when debugging missing autorelease pools
  20. //特殊情况:删除所有的pop
  21. //销毁当前页面
  22. page->kill();
  23. //将热页面标记设置为nil
  24. setHotPage(nil);
  25. } else if (page->child) {
  26. // hysteresis: keep one empty child if page is more than half full
  27. //如果页面超过一半,则保留一个空子页面
  28. if (page->lessThanHalfFull()) {
  29. page->child->kill();
  30. }
  31. else if (page->child->child) {
  32. page->child->child->kill();
  33. }
  34. }
  35. }

6.2 releaseUntil

进入releaseUntil函数

  1. void releaseUntil(id *stop)
  2. {
  3. // Not recursive: we don't want to blow out the stack
  4. // if a thread accumulates a stupendous amount of garbage
  5. //向下遍历,到stop停止
  6. while (this->next != stop) {
  7. // Restart from hotPage() every time, in case -release
  8. // autoreleased more objects
  9. //获取热页面
  10. AutoreleasePoolPage *page = hotPage();
  11. // fixme I think this `while` can be `if`, but I can't prove it
  12. //如果当前页面中没有对象
  13. while (page->empty()) {
  14. //获取父页面
  15. page = page->parent;
  16. //标记为热页面
  17. setHotPage(page);
  18. }
  19. page->unprotect();
  20. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  21. AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;
  22. // create an obj with the zeroed out top byte and release that
  23. id obj = (id)entry->ptr;
  24. int count = (int)entry->count; // grab these before memset
  25. #else
  26. //内存平移,获取对象
  27. id obj = *--page->next;
  28. #endif
  29. memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
  30. page->protect();
  31. //当前对象不是哨兵对象
  32. if (obj != POOL_BOUNDARY) {
  33. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  34. // release count+1 times since it is count of the additional
  35. // autoreleases beyond the first one
  36. for (int i = 0; i < count + 1; i++) {
  37. objc_release(obj);
  38. }
  39. #else
  40. //将其释放
  41. objc_release(obj);
  42. #endif
  43. }
  44. }
  45. //将当前页面标记为热页面
  46. setHotPage(this);
  47. #if DEBUG
  48. // we expect any children to be completely empty
  49. for (AutoreleasePoolPage *page = child; page; page = page->child) {
  50. ASSERT(page->empty());
  51. }
  52. #endif
  53. }

6.3 kill

进入kill函数

  1. void kill()
  2. {
  3. // Not recursive: we don't want to blow out the stack
  4. // if a thread accumulates a stupendous amount of garbage
  5. AutoreleasePoolPage *page = this;
  6. //循环找到最后一个子页面
  7. while (page->child) page = page->child;
  8. AutoreleasePoolPage *deathptr;
  9. do {
  10. deathptr = page;
  11. //找到父页面
  12. page = page->parent;
  13. if (page) {
  14. //将子页面设置为nil
  15. page->unprotect();
  16. page->child = nil;
  17. page->protect();
  18. }
  19. //销毁子页面
  20. delete deathptr;
  21. //遍历销毁到this为止
  22. } while (deathptr != this);
  23. }

7. 嵌套使用

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. NSObject *objc = [[[NSObject alloc] init] autorelease];
  4. @autoreleasepool {
  5. NSObject *objc = [[[NSObject alloc] init] autorelease];
  6. }
  7. _objc_autoreleasePoolPrint();
  8. }
  9. return 0;
  10. }
  11. -------------------------
  12. objc[2511]: ##############
  13. objc[2511]: AUTORELEASE POOLS for thread 0x1000ebe00
  14. objc[2511]: 4 releases pending.
  15. objc[2511]: [0x10680d000] ................ PAGE (hot) (cold)
  16. objc[2511]: [0x10680d038] ################ POOL 0x10680d038
  17. objc[2511]: [0x10680d040] 0x101370c40 NSObject
  18. objc[2511]: [0x10680d048] ################ POOL 0x10680d048
  19. objc[2511]: [0x10680d050] 0x101365fb0 NSObject
  20. objc[2511]: ##############
  • 可以嵌套使用

线程的自动释放池是一个指针堆栈,当嵌套使用时,添加好各自堆栈的哨兵对象。出栈时,先释放内部,再释放外部

8. ARC模式

将项目设置为ARC模式
image.png

创建LGPerson

  1. #import <Foundation/Foundation.h>
  2. @interface LGPerson : NSObject
  3. + (LGPerson *)person;
  4. @end
  5. @implementation LGPerson
  6. + (LGPerson *)person{
  7. LGPerson *p = [[LGPerson alloc] init];
  8. return p;
  9. }
  10. @end

main函数中,写入以下代码:

  1. extern void _objc_autoreleasePoolPrint(void);
  2. int main(int argc, const char * argv[]) {
  3. @autoreleasepool {
  4. LGPerson *p1 = [[LGPerson alloc] init];
  5. LGPerson *p2 = [LGPerson person];
  6. _objc_autoreleasePoolPrint();
  7. }
  8. return 0;
  9. }
  10. -------------------------
  11. objc[2699]: ##############
  12. objc[2699]: AUTORELEASE POOLS for thread 0x1000ebe00
  13. objc[2699]: 2 releases pending.
  14. objc[2699]: [0x10200e000] ................ PAGE (hot) (cold)
  15. objc[2699]: [0x10200e038] ################ POOL 0x10200e038
  16. objc[2699]: [0x10200e040] 0x1013658b0 LGPerson
  17. objc[2699]: ##############
  • 只有p2对象压栈到自动释放池

ARC模式,使用allocnewcopymutableCopy前缀开头的方法进行对象创建,不会加入到自动释放池。它们的空间开辟由开发者申请,释放也由开发者进行管理

临时变量的释放:

  • 正常情况下,超出其作用域就会立即释放

  • 加入自动释放池,会延迟释放。当Runloop休眠或超出autoreleasepool作用域之后释放

9. 与线程、Runloop的关系

参见官方文档:NSAutoreleasePool

9.1 与线程的关系

  • 每个线程(包括主线程)维护自己的对象堆栈。随着新池的创建,它们被添加到堆栈的顶部。当池被释放时,它们会从堆栈中移除

  • autoreleased对象被放置在当前线程的顶部自动释放池中。当一个线程终止时,它会自动清空所有与其关联的自动释放池

9.2 与Runloop的关系

  • 主程序在事件循环的每个循环开始时在主线程上创建一个自动释放池

  • 并在结束时将其排空,从而释放在处理事件时生成的任何自动释放对象

总结

概述:

  • AutoReleasePool:自动释放池

  • OC中的一种内存自动回收机制,它可以将加入AutoreleasePool中的变量release的时机延迟

  • Runloop在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中

  • 在一次完整的Runloop结束之前,会向自动释放池中所有对象发送release消息,然后销毁自动释放池

结构:

  • 自动释放池的压栈和出栈,通过结构体的构造函数和析构函数触发

  • 压栈:调用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获取当前线程

      • 链接双向链表

      • 设置为热页面

      • pushExtraBoundaryYES,哨兵对象压栈

      • 对象压栈

  • 对象出栈

    • 调用popPage函数,传入stop为哨兵对象的位置

    • 当前页中对象出栈,到stop位置停止

    • 调用kill函数,销毁当前页面

嵌套使用:

  • 线程的自动释放池是一个指针堆栈,当嵌套使用时,添加好各自堆栈的哨兵对象。出栈时,先释放内部,再释放外部

ARC模式:

  • ARC模式,使用allocnewcopymutableCopy前缀开头的方法进行对象创建,不会加入到自动释放池。它们的空间开辟由开发者申请,释放也由开发者进行管理

与线程的关系:

  • 每个线程(包括主线程)维护自己的对象堆栈。随着新池的创建,它们被添加到堆栈的顶部。当池被释放时,它们会从堆栈中移除

  • autoreleased对象被放置在当前线程的顶部自动释放池中。当一个线程终止时,它会自动清空所有与其关联的自动释放池

Runloop的关系:

  • 主程序在事件循环的每个循环开始时在主线程上创建一个自动释放池

  • 并在结束时将其排空,从而释放在处理事件时生成的任何自动释放对象