苹果设备备受欢迎的背后离不开iOS优秀的内存管理机制,那iOS的内存布局及管理方案是怎样的呢?咱们一块儿研究下。
栈区(stack):线性结构,内存连续,系统本身管理内存,程序运行记录,每一个线程,也就是每一个执行序列各有一个(看crash log最容易理解),都是编译的时候能肯定好的,还有一个特色就是这里面的数据能够不用指针,也不会丢。编程
堆区(heap):链式结构,内存不连续,最灵活的内存区,用途多多,动态分配和释放,编译时不能提早肯定,咱们的Objective-C对象都是这么来的,都存在这里,一般堆中的对象都是以指针来访问的,指针从线程栈中来,但不独属于某个线程,堆也是对复杂的运行时处理的基础支持,还有就是ARC仍是MRC、“谁分配谁释放”说的都是堆上对象的管理。swift
静态区(全局区)(bss):初始化数据,简单理解就是有初始值的变量、常量。数组
常量区(data):未初始化数据,只声明未给值的变量,运行前通通为0,之因此单独分出来,是出于性能的考虑,由于这些东西都是0,不必放在程序包里,也不用copy。数据结构
代码区(text):最静态的,就是只读的东西,存储代码。多线程
咱们详细看下每种方案的实现及存在的意义。ide
一.tagged pointer布局
没有这种管理机制会引发内存浪费,为何呢?咱们来看下,假设咱们要存储一个NSNumber对象,其值是一个整数。正常状况下,若是这个整数只是一个NSInteger的普通变量,那么它所占用的内存是与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的。而指针类型的大小一般也是与CPU位数相关,一个指针所占用的内存在32位CPU下为4个字节,在64位CPU下也是8个字节。性能
因此一个普通的iOS程序,若是没有Tagged Pointer对象,从32位机器迁移到64位机器中后,虽然逻辑没有任何变化,但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。以下图所示:spa
咱们再来看看效率上的问题,为了存储和访问一个NSNumber对象,咱们须要在堆上为其分配内存,另外还要维护它的引用计数,管理它的生命期。这些都给程序增长了额外的逻辑,形成运行效率上的损失。操作系统
为了改进上面提到的内存占用和效率问题,苹果提出了Tagged Pointer对象。因为NSNumber、NSDate一类的变量自己的值须要占用的内存大小经常不须要8个字节,拿整数来讲,4个字节所能表示的有符号整数就能够达到20多亿(注:2^31=2147483648,另外1位做为符号位),对于绝大多数状况都是能够处理的。
因此咱们能够将一个对象的指针拆成两部分,一部分直接保存数据,另外一部分做为特殊标记,表示这是一个特别的指针,不指向任何一个地址。因此,引入了Tagged Pointer对象以后,64位CPU下NSNumber的内存图变成了如下这样:
当8字节能够承载用于表示的数值时,系统就会以Tagged Pointer的方式生成指针,若是8字节承载不了时,则又用之前的方式来生成普通的指针。以上是关于Tag Pointer的存储细节。
Tagged Pointer的特色:
因而可知,苹果引入Tagged Pointer,不但减小了64位机器下程序的内存占用,还提升了运行效率。完美地解决了小内存对象在存储和访问效率上的问题。
2、Non-pointer iSA--非指针型iSA
在64位系统上只须要32位来储存内存地址,而剩下的32位就能够用来作其余的内存管理
non_pointer iSA 的判断条件:
1 : 包含swift代码;
2:sdk版本低于10.11;
3:runtime读取image时发现这个image包含__objc_rawi sa段;
4:开发者本身添加了OBJC_DISABLE_NONPOINTER_ISA=YES到环境变量中;
5:某些不能使用Non-pointer的类,GCD等;
6:父类关闭。
3、SideTables,RefcountMap,weak_table_t
为了管理全部对象的引用计数和weak指针,苹果建立了一个全局的SideTables,虽然名字后面有个"s"不过他实际上是一个全局的Hash表,里面的内容装的都是SideTable结构体而已。它使用对象的内存地址当它的key。管理引用计数和weak指针就靠它了。
由于对象引用计数相关操做应该是原子性的。否则若是多个线程同时去写一个对象的引用计数,那就会形成数据错乱,失去了内存管理的意义。同时又由于内存中对象的数量是很是很是庞大的须要很是频繁的操做SideTables,因此不能对整个Hash表加锁。苹果采用了分离锁技术。
下边是SideTabel的定义:
SideTable struct SideTable { //锁 spinlock_t slock; //强引用相关 RefcountMap refcnts; //弱引用相关 weak_table_t weak_table; ... }
当咱们经过SideTables[key]来获得SideTable的时候,SideTable的结构以下:
一、一把自旋锁。spinlock_t slock;
自旋锁比较适用于锁使用者保持锁时间比较短的状况。正是因为自旋锁使用者通常保持锁时间很是短,所以选择自旋而不是睡眠是很是必要的,自旋锁的效率远高于互斥锁。信号量和读写信号量适合于保持时间较长的状况,它们会致使调用者睡眠,所以只能在进程上下文使用,而自旋锁适合于保持时间很是短的状况,它能够在任何上下文使用。
它的做用是在操做引用技术的时候对SideTable加锁,避免数据错误。
苹果在对锁的选择上能够说是精益求精。苹果知道对于引用计数的操做实际上是很是快的。因此选择了虽然不是那么高级可是确实效率高的自旋锁
二、引用计数器 RefcountMap * refcnts;
对象具体的引用计数数量是记录在这里的。
这里注意RefcountMap实际上是个C++的Map。为何Hash之后还须要个Map呢?由于内存中对象的数量实在是太庞大了咱们经过第一个Hash表只是过滤了第一次,而后咱们还须要再经过这个Map才能精确的定位到咱们要找的对象的引用计数器。
引用计数器的数据类型是:
typedef __darwin_size_t size_t;
再进一步看它的定义实际上是unsigned long,在32位和64位操做系统中,它分别占用32和64个bit。
苹果常用bit mask技术。这里也不例外。拿32位系统为例的话,能够理解成有32个盒子排成一排横着放在你面前。盒子里能够装0或者1两个数字。咱们规定最后边的盒子是低位,左边的盒子是高位。
(1UL<<0)的意思是将一个"1"放到最右侧的盒子里,而后将这个"1"向左移动0位(就是原地不动):0b0000 0000 0000 0000 0000 0000 0000 0001
(1UL<<1)的意思是将一个"1"放到最右侧的盒子里,而后将这个"1"向左移动1位:0b0000 0000 0000 0000 0000 0000 0000 0010
下面来分析引用计数器(图中右侧)的结构,从低位到高位。
(1UL<<0)????WEAKLY_REFERENCED
表示是否有弱引用指向这个对象,若是有的话(值为1)在对象释放的时候须要把全部指向它的弱引用都变成nil(至关于其余语言的NULL),避免野指针错误。
(1UL<<1)????DEALLOCATING
表示对象是否正在被释放。1正在释放,0没有
(1UL<<(WORD_BITS-1))????SIDE_TABLE_RC_PINNED
其中WORD_BITS在32位和64位系统的时候分别等于32和64。其实这一位没啥具体意义,就是随着对象的引用计数不断变大。若是这一位都变成1了,就表示引用计数已经最大了不能再增长了。
三、维护weak指针的结构体 weak_table_t * weak_table;
第一层结构体中包含两个元素。
第一个元素weak_entry_t *weak_entries;是一个数组,上面RefcountMap是要经过find(key)来找到精确的元素的。weak_entries则是经过循环遍从来找到对应的entry。
(上面管理引用计数器苹果使用的是Map,这里管理weak指针苹果使用的是数组,有兴趣的朋友能够思考一下为何苹果会分别采用这两种不一样的结构)
这个是由于weak的显著的特征来决定的: 当weak对象被销毁的时候,要把全部指向该对象的指针都设为nil。
第二个元素num_entries是用来维护保证数组始终有一个合适的size。好比数组中元素的数量超过3/4的时候将数组的大小乘以2。
第二层weak_entry_t的结构包含3个部分
一、referent:被指对象的地址。前面循环遍历查找的时候就是判断目标地址是否和他相等。
二、referrers:可变数组,里面保存着全部指向这个对象的弱引用的地址。当这个对象被释放的时候,referrers里的全部指针都会被设置成nil。
三、inline_referrers只有4个元素的数组,默认状况下用它来存储弱引用的指针。当大于4个的时候使用referrers来存储指针。
上面咱们介绍了苹果为了更好的内存管理使用的三种不一样的内存管理方案,在内部采用了不一样的数据结构以达到更高效内存检索。
参考连接: https://www.jianshu.com/p/dcb...
http://www.cocoachina.com/art...
http://www.cocoachina.com/art...
参考书籍:Objective-C高级编程:iOS与OS X多线程和内存管理
欢迎点击“京东云”了解更多精彩内容