深入理解Autorelease Pool

作者: rhythmhuang

前言

MRC下,我们需要手动管理对象的retain和release,或者调用autorelease方法把对象放进AutoreleasePool中来进行内存管理。在ARC下,我们甚至都不用知道retain,release,autorelease这些方法就能管理好内存。尽管这样,深入了解AutoreleasePool对于我们解决一些棘手的问题还是很有必要,甚至是必须的。

比如上面的crash堆栈,在了解了autorelease的原理之后,就能够大概知道crash产生的原因以及解决方法。

举个小栗子

这里的输出是这样的:

这个例子说明了,当一个object加入了Autorelease Pool之后,它就不是在这个对象所在方法结束的时候release了。其实它的release会延迟到当前runloop执行完之后,为什么会这样呢?

Autorelease Pool是什么

我们平常使用到的Autorelease Pool的情景,大部分是这样写的:

在编译时,编译器会自动把它编译为如下形式:

在执行@autoreleasepool方括号中的代码前,会有一个push操作。在执行完@autoreleasepool方括号中的代码后,还会有一个pop的操作与之对应。通过分析NSObject源码,我们可以看到Autorelease Pool的真面目:

Autorelease Pool的本质上是一个双向链表。双向链表中每一页为一个AutoreleasePoolPage,AutoreleasePoolPage最大为4096B,每当AutoreleasePoolPage中因为存储变量总大小超过4096B之后,就会分配一个新的AutoreleasePoolPage:

AutoreleasePoolPage中的parent指针指向上一页,child指针指向下一页。next指针指向当前页中下一个可以存储变量的地址。AutoreleasePoolPage除了这些属性之外,开辟了一段连续的区域来记录放入Autorelease Pool的变量的地址。

对于像这样嵌套的@autoreleasepool代码,Autorelease Pool的双向链表是怎么使用的呢?

Autorelease Pool当遇到嵌套的@autoreleasepool时,会先插入一个“0”,称之为哨兵对象(POOL_SENTINEL)。嵌套内的@autoreleasepool中加入Autorelease Pool中的变量,会在哨兵对象之后插入AutoreleasePoolPage。当执行完嵌套内的@autoreleasepool之后,AutoreleasePool会调用objc_autoreleasePoolPop()方法把哨兵对象之前的变量全部出栈,恢复到进入嵌套内@autoreleasepool之前的状态。

再举一个小栗子

如果把objectWithNumber:方法中的__autoreleasing去掉,那么输出会有怎样的变化呢?

此时,生成的TextObject对象在viewDidLoad执行完之后,就马上被释放掉了,在viewWillAppear执行的时候,TextObject对象已经为null了。这是为什么呢?我们可以使用[NSAutoreleasePool showPools]方法来检查在这种情况下,Autorelease Pool的情况。

showPools

使用[NSAutoreleasePoolshowPools]方法可以在调试的时候方便的随时查看当前AutoreleasePool的变量存储情况。我们在需要解决AutoreleasePool相关的问题时可以使用它方便地定位问题。

有__autoreleasing时:

没有__autoreleasing时:

在使用__autoreleasing修饰符修饰初始化出来的变量时,变量会加入到Autorelease Pool中。

在没有使用__autoreleaseing修饰符修饰初始化出来的变量时,变量并没有加入到Autorelease Pool中。

线程局部存储

在Xcode中查看没有__autoreleasing修饰符时,初始化TestObject的方法,可以看到如下代码:

可以看到,初始化出来的TestObject并没有加入Autorelease Pool,而是调用了_objc_autoreleaseReturnValue()。

TLS(线程局部存储)则是一块属于某一线程的专有存储,使用Key-Value的形式读写。能以比autorelease更快的速度进行对象的存储和读取。

编译器在编译阶段会通过上下文判断初始化出来的对象。如果调用方与被调用方法都是ARC的,编译器会做一个流程上的优化,在被调用方使用_objc_autoreleaseReturnValue()把对象放到TLS上。而在调用方使用_objc_retainAutoreleasedReturnValue()读取TLS上的对应对象。使用TLS做中转,避免了把对象放进Autorelease Pool的retain和release操作。这相当于是编译器的一项优化。

小结

本文主要讲述了以下内容:

1.Autorelease Pool是以双向链表形式存储的。

2.在ARC模式下,编译器会使用TLS优化变量的存储。

参考资料

1.黑幕背后的Autorelease

2.ARC环境下编译器到底对autorelease对象做了怎样的优化

3.自动释放池的前世今生---- 深入解析 autoreleasepool

4.探索子线程autorelease对象的释放时机

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏积累沉淀

CXF 发布 Web Service

使用CXF框架开发 ①.CXF : xfire–>xfire + celtrix 做web service开发的开源框架 ②.开发Server端: 加入...

2066
来自专栏WOLFRAM

Mathematica使用中易犯的错误

3753
来自专栏牛肉圆粉不加葱

[源码剖析]Spark读取配置Spark读取配置

我们知道,有一些配置可以在多个地方配置。以配置executor的memory为例,有以下三种方式:

843
来自专栏HTML5学堂

面向对象系列讲解——混合模式

HTML5学堂:我们解释了面向对象以及使用面向对象的原因,并书写了最为基本的面向对象的代码。同时我们提出了一些问题,工厂模式存在着一些缺陷,在这篇文章当中我们就...

3386
来自专栏ios 技术积累

ios 自动释放池

OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机,即当我们创建了一个对象,并把他加入到了自动释放池中时,他...

1062
来自专栏牛肉圆粉不加葱

[Spark源码剖析] JobWaiter

来创建容纳job结果的数据,数组的每个元素对应与之下标相等的partition的计算结果;并将结果处理函数(index, res) => results(ind...

612
来自专栏Golang语言社区

动手实现一个JSON验证器(上)

分析 既然要验证JSON的有效性,那么必然需要清楚的知道JSON格式,这个在JSON官网已经给我们画出来了: ? ? ? ? ? 从官方的图上面可以看出,JSO...

4357
来自专栏Petrichor的专栏

python: set函数

1372
来自专栏FD的专栏

写出形似QML的C++代码

我的第一个想法(居然?)是做个Embedded-DSL。不过C++又不是Ruby……随便搜了一下,发现了一篇文章,也只是利用了重载运算符和运算符优先级,看上去限...

542
来自专栏岑玉海

Hbase 学习(三)Coprocessors

Coprocessors 之前我们的filter都是在客户端定义,然后传到服务端去执行的,这个Coprocessors是在服务端定义,在客户端调用,然后在服...

36111

扫码关注云+社区