自动释放池浅析

215 阅读2分钟

自动释放池

struct __AtAutoreleasePool {

  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}

  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}

  void * atautoreleasepoolobj;

};
  • objc_autoreleasePoolPush
  • objc_autoreleasePoolPop
  • 结构体中有一个构造函数和析构函数

objc_autoreleasePoolPush image.png

Autorelease池实现

线程的自动释放池是指针的堆栈。
每个指针要么是一个要释放的对象,要么是POOL_BOUNDARY 自动发布池边界。
池令牌是指向该池的POOL_BOUNDARY的指针。当 池被弹出,每个比哨兵温度高的对象都被释放。
堆栈被划分为一个双链接的页面列表。页面添加 必要时删除。
线程本地存储指向热页面,其中新自动释放 对象存储。

AutoreleasePoolPage image.png

AutoreleasePoolPageData image.png magic : 用来校验AutoreleasePoolPage的结构是否完整 next : 指向最新添加的autoleased对象的下一个位置,初始化时指向begin() thread : 指向当前线程 parent : 指向父节点,第一个节点的parent值为nil child : 指向子节点,最后一个节点的child值为nil depth : 代表深度,从0开始往后递增1 hiwat : 代表high water makrk 最大入栈数量标记

  • 工程中关闭ARC image.png

  • 编写代码运行调试 image.png

自动释放池的创建和压栈

  • push image.png 这里在构造压栈的第一个就放入了一个对象是pool_boundary,是一个哨兵对象。

  • autoreleaseFast image.png

  • autoreleaseNoPage image.png

page->add(POOL_BOUNDARY);加入哨兵对象

  • 双向链表的构造 image.png

image.png

  • begin() 开辟内存 image.png 这里的this的内存大小是56

image.png magic = 16, next = 8, thread = 8, parent= 8, child = 8, depth = 4, hiwat = 4,

image.png 这里的static 是全局区所以这个结构体的大小是 4*4的数组 是16 字节 所以这里符合 56字节的内存大小

  • objc_thread_self() 获取当前线程 image.png

  • 重新回看这个结果打印 image.png 这里可以清晰的发现在地址 0x10180b000处开始,0x10180b038这里加入的是一个哨兵对象,中间差了56个字节,在哨兵对象后才加入了第一个NSObject对象0x10180b040


  • autoreleaseFullPage image.png 这里是如果自动释放池满了就会一直地轨到最后一页创建新的page,设置为hote

  • 创建的结构示意图如图所示 image.png

自动释放池的满页临界点

image.png

image.png

image.png 是1左移12位即2的12次方为4096.为4K.

  • 测试 image.png

image.png 这里循环压栈如505个对象,发现这里新创建了page,这里可以计算验证504个对象+1个哨兵对象+56字节的默认对象 504*8+8+56=4096.同时也发现了在创建了新的page后是不在会创建哨兵对象的。所以哨兵对象是只有一个的。

自动释放池出栈

image.png

image.png 链表结构向上翻页将父页设置为Hot,调用kill删除本页。

image.png do-while循环不断的向前删除。

image.png 遇到哨兵边界后停止pop.