深入理解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 条评论
登录 后参与评论

相关文章

来自专栏前端真相

前端编码规范

2576
来自专栏Golang语言社区

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

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

5297
来自专栏ios 技术积累

ios 自动释放池

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

2122
来自专栏Java面试通关手册

Java多线程学习(三)volatile关键字

Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):https://github.com/Snailclimb/Java_G...

1963
来自专栏一个小程序员的成长笔记

自定义类型的创建

 以上方法,对于有其他OO语言经验的开发人员去看,比较容易理解,但是性能上并不推荐。因为每次创建新的实例都需要进行一次判断,哪怕这次的性能损耗是极小的,但毕竟也...

982
来自专栏积累沉淀

CXF 发布 Web Service

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

2406
来自专栏difcareer的技术笔记

JNI实现源码分析【四 函数调用】正文0x01:dvmCallMethodV0x02:nativeFunc0x03: 何时赋值

有了前面的铺垫,终于可以说说虚拟机是如何调用JNI方法的了。JNI方法,对应Java中的native方法,所以我们跟踪对Native方法的处理即可。

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

Spark的位置优先: TaskSetManager 的有效 Locality Levels

在Spark Application Web UI的 Stages tag 上,我们可以看到这个的表格,描述的是某个 stage 的 tasks 的一些信息,其...

1343
来自专栏Petrichor的专栏

python: set函数

2552
来自专栏黑泽君的专栏

day19_java基础加强_动态代理+注解+类加载器

        Proxy Pattern(即:代理模式),23种常用的面向对象软件的设计模式之一。         代理模式的定义:为其他对象提供一种代理以控...

1264

扫码关注云+社区

领取腾讯云代金券