苹果设备备受欢迎的背后离不开iOS优秀的内存管理机制,那iOS的内存布局及管理方案是怎样的呢?我们一起研究下。

内存管理分为五大块

栈区(stack):线性结构,内存连续,系统自己管理内存,程序运行记录,每个线程,也就是每个执行序列各有一个(看crash log最容易理解),都是编译的时候能确定好的,还有一个特点就是这里面的数据可以不用指针,也不会丢。

堆区(heap):链式结构,内存不连续,最灵活的内存区,用途多多,动态分配和释放,编译时不能提前确定,我们的Objective-C对象都是这么来的,都存在这里,通常堆中的对象都是以指针来访问的,指针从线程栈中来,但不独属于某个线程,堆也是对复杂的运行时处理的基础支持,还有就是ARC还是MRC、“谁分配谁释放”说的都是堆上对象的管理。

静态区(全局区)(bss):初始化数据,简单理解就是有初始值的变量、常量。

常量区(data):未初始化数据,只声明未给值的变量,运行前统统为0,之所以单独分出来,是出于性能的考虑,因为这些东西都是0,没必要放在程序包里,也不用copy。

代码区(text):最静态的,就是只读的东西,存储代码。

iOS内存管理方案有三种

我们详细看下每种方案的实现及存在的意义。

一.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一类的对象所占用的内存会翻倍。如下图所示:

我们再来看看效率上的问题,为了存储和访问一个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的特点:

  1. 我们也可以在WWDC2013的《Session 404 Advanced in Objective-C》视频中,看到苹果对于Tagged Pointer特点的介绍:Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate, 当然NSString小于60字节的也可以运用了该手段
  2. Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已,因为他没有isa指针。所以,它的内存并不存储在堆中,也不需要malloc和free。
  3. 在内存读取上有着3倍的效率,创建时比以前快106倍。

由此可见,苹果引入Tagged Pointer,不但减少了64位机器下程序的内存占用,还提高了运行效率。完美地解决了小内存对象在存储和访问效率上的问题。

二、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:父类关闭。

三、SideTables,RefcountMap,weak_table_t

为了管理所有对象的引用计数和weak指针,苹果创建了一个全局的SideTables,虽然名字后面有个"s"不过他其实是一个全局的Hash表,里面的内容装的都是SideTable结构体而已。它使用对象的内存地址当它的key。管理引用计数和weak指针就靠它了。

因为对象引用计数相关操作应该是原子性的。不然如果多个线程同时去写一个对象的引用计数,那就会造成数据错乱,失去了内存管理的意义。同时又因为内存中对象的数量是非常非常庞大的需要非常频繁的操作SideTables,所以不能对整个Hash表加锁。苹果采用了分离锁技术。

下边是SideTabel的定义:

SideTablestruct SideTable {//锁spinlock_t slock;//强引用相关RefcountMap refcnts;//弱引用相关weak_table_t weak_table;...}

当我们通过SideTables[key]来得到SideTable的时候,SideTable的结构如下:

1、一把自旋锁。spinlock_t slock;

自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。

它的作用是在操作引用技术的时候对SideTable加锁,避免数据错误。

苹果在对锁的选择上可以说是精益求精。苹果知道对于引用计数的操作其实是非常快的。所以选择了虽然不是那么高级但是确实效率高的自旋锁

2、引用计数器 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了,就表示引用计数已经最大了不能再增加了。

3、维护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个部分

1、referent:被指对象的地址。前面循环遍历查找的时候就是判断目标地址是否和他相等。

2、referrers:可变数组,里面保存着所有指向这个对象的弱引用的地址。当这个对象被释放的时候,referrers里的所有指针都会被设置成nil。

3、inline_referrers只有4个元素的数组,默认情况下用它来存储弱引用的指针。当大于4个的时候使用referrers来存储指针。

上面我们介绍了苹果为了更好的内存管理使用的三种不同的内存管理方案,在内部采用了不同的数据结构以达到更高效内存检索。

参考书籍:Objective-C高级编程:iOS与OS X多线程和内存管理
最后推荐个我的iOS交流群:642363427有一个共同的圈子很重要,结识人脉!里面都是iOS开发,全栈发展,欢迎入驻,共同进步!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

multiprocessing.manager管理的对象需要加锁吗_iOS内存管理布局-理论篇相关推荐

  1. multiprocessing.manager管理的对象需要加锁吗_iOS内存管理布局及管理方案理论篇

    苹果设备备受欢迎的背后离不开iOS优秀的内存管理机制,那iOS的内存布局及管理方案是怎样的呢?我们一起研究下. 内存管理分为五大块 栈区(stack):线性结构,内存连续,系统自己管理内存,程序运行记 ...

  2. multiprocessing.manager管理的对象需要加锁吗_Go: 内存管理和分配

    本文基于Go1.13 当不再使用内存时,标准库会自动执行Go的内存管理即从分配到回收.尽管开发者不需要处理它,但是Go的底层管理进行了很好的优化并且充满了有趣的概念. 堆上的分配 内存管理被设计可以在 ...

  3. Linux内存管理:一个故事看懂CPU内存管理技术

    目录 8086 32位时代 虚拟内存 分页交换 现在 往期热门回顾 推荐阅读 还记得我吗,我是阿Q,CPU一号车间的那个阿Q. 今天忙里偷闲,来到厂里地址翻译部门转转,负责这项工作的小黑正忙得满头大汗 ...

  4. Spark 内存管理详解(下):内存管理

    本文转自:Spark内存管理详解(下)--内存管理 本文最初由IBM developerWorks中国网站发表,其链接为Apache Spark内存管理详解 在这里,正文内容分为上下两篇来阐述,这是下 ...

  5. python中内存管理机制一共分为多少层_python 内存管理机制

    内存管理机制 ​python中万物皆对象,python的存储问题是对象的存储问题,并且对于每个对象,python会分配一块内存空间去存储它 ​Python的内存管理机制:引入计数.垃圾回收.内存池机制 ...

  6. Java内存管理和客户加载过程_Java内存管理的进一步理解-模拟过程图解

    java的内存管理分为: 1.堆内存:2.栈内存:3.方法区:4.本地方法区 下面通过一个简单的代码示例,理解Java中,内存是怎么进行分配与管理的.示例如下: public classJavaRam ...

  7. python内存管理错误的是_解读Python内存管理机制(转载)

    内存管理,对于Python这样的动态语言,是至关重要的一部分,它在很大程度上甚至决定了Python的执行效率,因为在Python的运行中,会创建和销毁大量的对象,这些都涉及到内存的管理. 小块空间的内 ...

  8. arm的linux怎么管理任务,【linux】arm mm内存管理

    欢迎转载,转载时请保留作者信息,谢谢. arm mmu硬件原理 由上图,arm分四种模式,section,大小页+ 极小页,  section模式简单,也能说明mmu本质,其它模式只是用了多级数组索引 ...

  9. python内存管理错误的是_关于Python内存管理,下列说法错误的是

    53 Python中变量可以不指定类型,会自动根据赋值语句来决定类型 同时,使用前必须赋值,在赋值的同时变量也就创建了 发表于 2018-02-28 22:50:02 回复(0) 61 本题答案选 B ...

最新文章

  1. linux 故障:df -h统计磁盘空间占用太多,但又du -h找不到大的文件
  2. /proc/xx/maps命令
  3. 帧布局(FrameLayout)及属性
  4. texmaker中图片过大怎么办_【社工面试】社区居民楼起火,你怎么办?
  5. 【Linux】一步一步学Linux——Linux内核版本和发行版本(03)
  6. linux下python脚本print中文显示不正确_在终端上运行python脚本,没有打印或显示-为什么?...
  7. Git教程~忽略特殊文件
  8. ASP.NET Web API中的返回值
  9. 为什么谷歌要执行严格的代码编写规范
  10. Python:集合、三元运算符
  11. angular使用动态组件后属性值_Angular 2-组件
  12. pb预览状态下的pagecount_QuickLook高效文件预览神器,方便到令你意想不到
  13. 微软编程一小时--微软2014实习生招募编程模拟测试感想
  14. python模块安装包_制作python模块安装包[转载自刘天斯]
  15. 2018n年全国计算机考试,2018ncre全国计算机等级考试报名系统
  16. jenkins调查总结
  17. word中页眉页脚问题处理方法
  18. ubuntu20.04基础入门日记V1.0
  19. element-ui表格求和求平均数
  20. JVM垃圾回收——三色标记法

热门文章

  1. 新疆出差——特色美食大合集
  2. 【Redis学习】Redis的安装、管理、适用场合以及使用
  3. 【Java线程】线程协作实现多对多聊天
  4. 运维学习之进程的定义及其命令的使用
  5. servlet监听完成统计在线人数,显示在线人员列表(附源码)
  6. 基于EM的多直线拟合实现及思考
  7. Linux 档案权限概念
  8. linux实例大全学习笔记1
  9. 如何搞定putty乱码
  10. [转]大话企业级Android应用开发实战 音乐播放器的开发