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

相关文章

来自专栏逸鹏说道

ConcurrentDictionary 对决 Dictionary+Locking

在 .NET 4.0 之前,如果我们需要在多线程环境下使用 Dictionary 类,除了自己实现线程同步来保证线程安全之外,我们没有其他选择。 很多开发人员肯...

2717
来自专栏三丰SanFeng

Linux64位程序移植

1 概述 Linux下的程序大多充当服务器的角色,在这种情况下,随着负载量和功能的增加,服务器所使用内存必然也随之增加,然而32位系统固有的4GB虚拟地址空间限...

1857
来自专栏進无尽的文章

设计模式| 行为型模式 (上)

行为型模式共十一种:策略模式、模板方法模式、观察者模式、迭代器模式、解释器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式。 分两篇文...

742
来自专栏.NET技术

GC的前世与今生

  虽然本文是以.NET作为目标来讲述GC,但是GC的概念并非才诞生不久。早在1958年,由鼎鼎大名的图林奖得主John McCarthy所实现的Lisp语言就...

933
来自专栏张善友的专栏

.NET不可变集合已经正式发布

微软基础类库(Base Class Library)团队已经完成了.NET不可变集合的正式版本,但不包括ImmutableArray。与其一起发布的还包括针对其...

18710
来自专栏Linux驱动

协处理器CP15介绍—MCR/MRC指令(6)

概述:在基于ARM的嵌入式应用系统中,存储系统的操作通常是由协处理器CP15完成的。CP15包含16个32位的寄存器,其编号为0~15。 而访问CP15寄存器的...

2389
来自专栏Java Edge

UML 类图1 类

在UML 2.0的13种图形中,类图是使用频率最高的UML图之一。Martin Fowler在其著作《UML Distilled: A Brief Guide ...

481
来自专栏用户2442861的专栏

java保留两位小数

四舍五入   double   f   =   111231.5585;   BigDecimal   b   =   new   BigDecimal(f...

482
来自专栏腾讯移动品质中心TMQ的专栏

内存泄漏漫谈

对于C/C++来说,内存泄漏问题一直是个很让人头痛的问题,因为对于没有GC的语言,内存泄漏的概率要比有GC的语言大得多,同时,一旦发生问题,也严重的多,而且,内...

2077
来自专栏熊二哥

GOF设计模式快速学习

这段时间,学习状态比较一般,空闲时基本都在打游戏,和研究如何打好游戏,终于通过戏命师烬制霸LOL,玩笑了。为了和"学习"之间的友谊小船不翻,决定对以往学习过的G...

1779

扫码关注云+社区