知识点

iOS内存布局&内存管理方案&数据结构

2021-07-25  本文已影响0人  宋唐不送糖

内存布局

上面的图代表一个内存区域,内存区域分为内核区的内存(最上边),程序加载的控件(中间),保留的内存空间(最下面)。
地址的表示是由下到上是低地址到高地址。

比如说程序加载到内存会分成三段:未初始化区,已初始化区和代码段:

不同内存段分别代表的详细含义:
stack: 代表栈区,栈区一般都是方法调用会在这个内存区进行展开。
heap: 代表堆区,通过alloc等分配的对象,实际上都是在堆上面体现的。
bss: 未初始化的全局变量/静态变量等。
data: 已初始化的全局变量等。
text: 程序代码,加载到内存后都放在text段中。

内存管理方案

iOS操作系统是怎么对内存进行管理的?

iOS操作系统是针对不同场景,会提供不同的内存管理方案,有以下几种方案:
1.TaggedPointer
对一些小对象,如NSNumber等,采用的是TaggedPointer这种内存管理方案。
2.NONPOINTER_ISA
对于64位架构下的iOS应用程序采用的是NONPOINTER_ISA这种内存管理方案。
在64位架构下,ISA这个指针本身是占64个bit位的,但其实有32位或者40位就够用了,剩余的bit位其实是浪费的,苹果为了提高内存的利用率,在iSA剩余的这些bit位当中,存储了一些关于内存管理方面的相关内容,这个叫非指针型的ISA。
3.散列表
是一种很复杂的结构,其中包含了引用计数表和弱引用表。

NONPOINTER_ISA (非指针型的ISA)这种内存管理方案)


在arm64架构下,ISA指针一共有64个bit位,我们逐一分析这64个bit位分别都存储了什么内容:
首先看0-15位:第一位是(indexed)标志位,如果这个位置是0,代表我们使用的这个ISA指针只是一个纯的ISA指针,里面内容代表当前对象的类对象的地址。若这个标志是1,代表ISA指针里面不仅存储类对象的地址,还有一些内存管理方面的数据。

第二位(has_assoc)表示当前对象是否有关联对象,若是0则没有,若是1代表有。

第三位(has_cxx_dtor)表示当前对象是否有使用到C++相关的代码
剩下的33位(shiftcls)0,1的一个bit位表示当前对象的类对象的指针地址。

再后6位(magic),不影响内存管理的解答。

再后一位(weakly_referenced)标识了这个对象是否有相应的一个弱引用指针。

再后一位(deallocating)标识当前对象是否正在进行dealloc操作。

再后一位(has_siderable_rc)标识当前ISA指针中存储的引用计数是否达到了上线,若达到了,需要外挂一个sidetable这样的数据结构来存储相关的引用计数内容,也就是我们接下来要了解的散列表。

剩余的(extra_rc)代表的就是额外的引用计数,当引用计数值很小的时候,会存在ISA指针中,当大的时候,会有单独的引用计数表去存储。

通过NONPOINTER_ISA 64个bit位的分析,可以看出,关于内存管理不仅仅是散列表,其实还有ISA部分的extra_rc来存储相关的引用计数值。

散列表方式(关于散列表这种内存管理方案的相关面试问题)

散列表的方案在源码中是通过Side Tables()结构来实现,Side Tables()结构是什么:

Side Table()结构下挂了很多Side Table这样的数据结构,这些结构在不同的架构下有不同的个数。
例如在非嵌入式系统下面,一共有64个Side Table表。
Side Table()实际上是一个哈希表
可以通过一个对象指针来具体找到对应的引用计数标或弱引用表在哪一张Side Table中。

Side Table结构下包含了三个元素
1.自旋锁
2.引用计数表
3.弱引用表

面试当中进程会针对引用计数表和弱引用表提出一些相关技术问题。
也会有一些涉及到自旋锁的相关面试问题,不过涉及到一些多线程的和资源竞争方面相关的问题。

为什么不是一个Side Table来实现,而是由多个Side Table共同组成一个Side Tables()这样一个数据结构?

假如只有一张Side Table,相当于我们在内存当中分配的所有对象的引用计数或者说弱引用都放到了一张大表中,
如果要操作某个对象的引用计数值进行修改(进行+1或者-1的操作),
由于所有的对象可能是在不同的线程中分配创建的(包括调用他们的return或者release等方法,也可能是在不同线程里面进行操作的),
那么对这张表操作时就需要进行加锁处理,来保证数据访问的安全,这样就存在了效率问题
假如用户的内存空间一共有4GB,我们可能分配出成千上百万个内存对象,
如果每一个对象在进行引用计数改变时,都操作这张表,很显然就存在了效率问题。
当对象A操作时,因为加了锁,下一个对象就要等当前对象操作完之后,将锁释放后,B才能操作。
系统为了解决这样的效率问题,引用了分离锁的技术方案。

分离锁:可以把内存对象所对应的引用计数表分拆成多个部分,假设分拆成8个,需要对8个表分别加锁,
假如对象A在表1中,对象B在表2中,当A和B同时进行引用计数操作时,可以并发操作,
但如果只有一张表就只能按顺序操作,分离锁可以提高访问效率.

如何实现快速分流(如何通过一个对象指针,如何快速定位到它属于哪张Side Table表)?
快速分流是指: Side Table本质是张Hash表,这张Hash表中,可能有64张具体的Side Table,
存储不同对象的引用计数表和弱引用表

Hash表的概念是:(哈希查找 、哈希算法)

对象指针可以作为一个key
经过Hash函数的一个运算,会计算出一个值Value,来决定出这个对象所对应的Side Table是哪张,或者说在数组的位置索引是哪个。

下面看下Hash查找的过程

假如给定的值是对象内存地址,目标值是Side Table结构(数组)下标索引

为什么要通过Hash查找?

散列表数据结构(有关散列表实现的内存管理方案涉及到的一些数据结构)

1.自旋锁Spinlock_t(你是否使用过自旋锁,自旋锁和普通锁有什么区别,自旋锁有哪些使用场景呢?)

2.引用计数表RefcountMap


1.引用计数表是哈希表,可以理解为是一个字典,
可以通过指针,找到对应对象的引用计数,这个查找过程是一个哈希查找。
2.这个哈希算法实际上是对传入对象的指针做一个伪装的操作,然后去获取对应的引用计数(size_t)。
3.之所以使用哈希查找,是为了提高查找效率。
4.size_t表达的就是对象的引用计数值,是一个无符号long型的变量。

size_t每一个bit为代表的含义:


假如引用计数存储是用64位来表示的

3.弱引用表weak_table_t

上一篇下一篇

猜你喜欢

热点阅读