底层原理-33-自动释放池

目前我们内存管理都在在ARC环境下进行自动管理对象的引用计数,我们下面看下autoreleasepool

1.autoreleasepool

1.1 release与autorelease

对象在调用release方法的时候引用计数会立刻减1
而对象在调用autorelease方法时,对象的引用计数会在稍后的时间内减1,通常是在当前线程下一个“事件循环”。autorelease方法在方法返回对象的时候特别有用。如果方法中创建的对象在返回前执行release方法,那有可能对象还没有返回就被销毁了。使用autorelease方法,可以保证对象在返回的时候依然存活,也无需调用者手动释放对象,因为在当前线程的下一个事件循环,对象的引用计数会自动减1.autorelease方法可以延长对象的生命周期,可以跨越方法调用边界之后存活一段时间。

1.2 自动释放池

1、释放对象有两种方式,一种是调用release方法,调用release方法的对象引用计数会立刻递减;另一种是调用autorelease方法,将对象加入到“自动释放池”中,自动释放池中的对象会在稍后的某个时候被释放清空自动释放池时系统会向池中的对象发送release消息
2、自动释放池创建于左花括号,并于右花括号处自动清空,花括号范围内的对象,会于花括号的末尾处收到release消息
3、自动释放池可以降低应用程序的内存峰值
4、我们不需要担心自动释放池的创建问题。主线程或者GCD创建的线程,默认都有自动释放池,每一次执行事件循环的时就会将其清空

image.png

1.3 Clang分析

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        // insert code here...

        NSLog(@"Hello, World!");

    }

    return 0;

}
复制代码

在main函数会把我们的代码加入autoreleasepool中,Clang结果如下:

int main(int argc, const char * argv[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2f_kt02d2w10p55r2z7mn5gc7qw0000gn_T_main_904163_mi_0);

    }

    return 0;

}
/*析构*/
struct __AtAutoreleasePool {

  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}

  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}

  void * atautoreleasepoolobj;

};
复制代码

autoreleasepool相当于一个对象,本质上。

  • __AtAutoreleasePool是一个结构体,有构造函数 + 析构函数,结构体定义的对象在作用域结束后,会自动调用析构函数
  • 其中{} 是 作用域 ,优点是结构清晰,可读性强,可以及时创建销毁

1.4 AutoreleasePoolPage

看下autoreleasepool的源码描述

image.png 我们在clang的时候知道,自动释放池是一个析构函数, 构造的时候调用:objc_autoreleasePoolPush()析构的时候调用objc_autoreleasePoolPop()

image.png 自动释放池调用是通过AutoreleasePoolPage进行进行pushpop的。 其中页的大小为4096 image.png AutoreleasePoolPage是一个class,有一些方法和自己的析构函数,对页的一些操作大致如下:

image.png AutoreleasePoolPage继承于AutoreleasePoolPageData,我们看下

image.png

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

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

  • thread 指向当前线程;

  • parent 指向父结点,第一个结点的 parent 值为 nil ;

  • child 指向子结点,最后一个结点的 child 值为 nil ;

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

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

发现其中有AutoreleasePoolPage对象,所以有以下一个关系链AutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPage,从这里可以说明自动释放池除了是一个页,还是一个双向链表结构。

2. objc_autoreleasePoolPush

image.png 判断当前是否有释放池,没有的话创建一个,有的话压栈一个POOL_BOUNDARY.

  • 继续看下 autoreleaseNewPage

image.png

  • 查看hotPage()

image.png

  • 查看autoreleaseNoPage创建page

image.png

  • 查看初始化自动释放池页AutoreleasePoolPage

image.png 其中begin()表示压入对象开始的位置,表示AutoreleasePoolPage自己占有56大小

image.png

  • 查看自动释放池内存结构

在ARC环境下,无法手动调用autorelease,将Demo切换至MRC模式(Build Settings -> Objectice-C Automatic Reference Counting设置为NO

image.png 我们for循环压入打印自动释放池情况

image.png 页的地址0x7fbcc2810000和边界地址0x7fbcc2810038相差16进制下3*16+8 =56,刚好是AutoreleasePoolPage自己本身的内存大小。 我们加入505个对象

image.png image.png 第一页存储了504个对象,第二页存储了1个对象,第二页不再存储边界哨兵了。我们继续for循环1010个对象。 第一页 image.png 第二页

image.png 第三页

image.png 增加了505个对象,增加了一页,说明一页可以存储505个对象,大小为505*8 = 4040大小,页本身大小为56 所以一页的大小为4096和红定义一样。首页因为存储了哨兵对象所以少存储一个对象

image.png

  • 压栈对象 autoreleaseFast

image.png 进入autoreleaseFast源码,主要有以下几步:

  1. 获取当前操作页,并判断页是否存在以及是否满了
  2. 如果页存在,且未满,则通过add方法压栈对象
  3. 如果页存在,且满了,则通过autoreleaseFullPage方法安排新的页面
  4. 如果页不存在,则通过autoreleaseNoPage方法创建新页
  • autoreleaseFullPage分析

image.png

  1. 遍历当前页的子页面链,直到没有子页面创建一个页面
  2. 设置创建的页为hotPage操作页面
  3. 向创建的页添加对象,压栈
  • autorelease 底层分析

autorelease->rootAutorelease->rootAutorelease2->autorelease(id obj)

image.png 最终还是执行autoreleaseFast进行压栈操作,和压栈哨兵对象一样进入autoreleaseFast只是对象不同。

3. objc_autoreleasePoolPop

objc_autoreleasePoolPop方法中有个参数,在clang分析时,发现传入的参数是push压栈后返回的哨兵对象,即ctxt,其目的是避免出栈混乱,防止将别的对象出栈 查看pop源码

image.png

  1. 空页面的处理,并根据token获取page
  2. 容错处理
  3. 通过popPage出栈页
  • popPage查看

image.png 不是哨兵对象的话通过popPage<false>(token, page, stop)出栈操作,通过releaseUntil出栈当前操作页面对象,释放到stop位置之前的所有对象,之后对当前页进行判断是否为空,进行相关页kill

  • releaseUntil查看

image.png 通过while循环遍历出当前stop的对象之前所有的object,哨兵除外进行objc_release出栈操作,其中嵌套一个while循环,不断向前父节点页遍历。

image.png 进入releaseUntil

image.png 之后从后面的页,一页一页往前进行release页中的对象,直到stop对象,不是哨兵对象就进行release

image.png

4. 总结

1.自动释放池主要是为我们管理对象内存,不用我们手动的进行release操作,而是由释放池在下次事件循环时候把释放池中对象进行release操作,会延缓对象的生命周期
2. 自动释放池是一个对象,自身有一个析构函数,创建的时候进行压栈操作push析构的时候进行出栈操作pop。继承于autorelseasePoolPage类,poolPage本身继承于结构体autoreleasePoolPageData,大小为56字节。创建page后会插入一个POOL_Boundary(哨兵对象)主要是为了防止越界,但是只有首页会插入哨兵对象。一页最多可插入505个对象,页的大小为505*8+56(页本身大小) = 4096字节,poolPage是一个双向链表的结构,它有一个父节点一个子节点,首页的父节点为空,末页的子节点为空。
3.autorelseasePoolPage加入对象通过push进行操作,没有页的话创建页同时传入哨兵对象,这一步在构造的时候就会调用。之后页满了的话通过autoreleaseNoPage创建新的页,通过add的方法加入对象。
4. 通常在析构的时候调用pop方法,会传入哨兵对象,我们根据stop的位置,释放stop对象之前所有的对象,哨兵除外。通过while循环当前页的---next往上依次release对象,当前页的话就把hotpage设置为父节点页面,重复之前操作直到stop对象

猜你喜欢

转载自juejin.im/post/7008744916369014798