内存管理

  • 内存布局
  • 内存管理方案
  • 数据结构
  • ARC & MRC
  • 引用计数
  • 弱引用
  • 自动释放池
  • 循环引用

一、内存布局

image
* stack 方法调用
* heap 通过alloc分配的对象
* bss 未初始化的全局变量等
* data 已初始化的全局变量等
* text 程序代码

二、内存管理方案

  • TaggedPointer ---- 一些小对象,如 NSNumber
  • NONPOINTER_ISA ---- 64位架构下的 ios 程序
  • 散列表 ---- 复杂的数据结构,包括引用计数表和弱引用表

三、数据结构

1. NONPOINTER_ISA

image
  • indexed -- 标志位,若为0:此 isa 指针为纯的 isa 指针,里面的内容代表了当前对象类对象的地址;若为1:为非指针型的 isa ,不仅存储了类对象的地址,而且还有内存管理的数据
  • has_assoc -- 是否有关联对象;0-无;1-有;
  • has_cxx_dtor -- 当前对象是否使用到 c++ 相关代码
  • shiftcls -- 当前对象类对象的指针地址,共33位
  • magic -- 对内存管理无影响
  • weakly_referenced -- 此对象是否有弱引用指针
  • deallocating -- 是否有 dealloc 操作
  • has_sidetable_rc -- 当前引用计数是否达到上限,是否引用散列表计数
  • extra_rc -- 额外引用计数(若超出则使用散列表存储)

2. 散列表

SideTables()结构 -- 实际为一个哈希表


image

SideTable结构


image
问题:为什么SideTable是好几张表,而不是一张表:

假如是一张表的话,系统的所有对象都会在一张表中,那么当对其进行操作的时候,那么必然需要加锁,当有大量相似操作的时候,效率会大大降低;多张表相当于多线程操作,可以提高效率

问题:怎样实现快速分流(怎样根据key找到SideTable位置):

SideTables本质是一张Hash表,通过哈希查找找到下表


image

例如:给定值是对象的内存地址,目标值是数组下标索引


image

不会遍历所有的表,所以会提高查找效率。

3.散列表的数据结构

  • 自旋锁 --- spinlock_t
  • 引用计数表 --- RefcountMap
  • 弱引用表 --- weak_table_t
自旋锁
  • 是“忙等”的锁。即若当前所被其他线程所获取,当前线程会不断探索这个锁是否被释放,若被释放会第一时间获取;
  • 适用于轻量访问。例如对当前对象做加一减一操作;
引用计数表
  • 是一个哈希表,为了查找提高查找效率,其中插入和获取都是通过同一个哈希算法来实现的,从而避免了 for 循环遍历。
image
size_t
image

实际是一个无符号的long型的值;第一位是是否有弱引用;第二位是是否有dealloc;其它位是引用计数位,所以在计算引用计数位的时候,需要向右偏移两位;

弱引用表
  • 也是一张哈希表
image

四、MRC、ARC

MRC

手动引用计数进行对象的内存管理


image

其中标红的是MRC特有的方法

ARC

  • ARC 是 LLVM(编译器)Runtime 协作的结果
  • ARC 中禁止手动调用 retain/release/retainCount/dealloc
  • ARC 中新增 weak、strong 关键字

五、引用计数管理

实现原理分析

  • alloc
  • retain
  • release
  • retainCount
  • dealloc

1、alloc实现

经过一系列的调用,最终调用了C函数 calloc;
此时并没有设置引用计数位1;

2、retain实现

image
  • 通过两次哈希查找;
  • 第一次找到 SideTables 中对应的 SideTable 的位置,然后获取对应的引用计数表,
  • 第二次查找是查找引用计数表中存储的引用计数值是 size_t 类型
  • 然后对 size_t 进行加一操作。(其中,SIDE_RC_ONE 并非是1,而是4,因为前两位存储的并不是引用计数相关内容,需要右移2位)

3、release实现

image
  • 与 retain 操作基本相同,最后进行减一操作

4、retainCount实现

image
  • 首先声明一个局部变量为 1 ;
  • 然后查找引用计数表中的引用计数
  • 最后进行位与操作,获取引用计数
问题:新alloc的对象为什么引用计数为1?
解答:因为,新alloc出来的对象,在引用计数表中是没有数值的,所以上面 it->second 读出的值为0,又声明的局部变量为1,所以相加之后 retainCount 为1.

5、dealloc实现

image

object_dispse()的实现:


image

objc_destructInstance() (销毁实例对象)的实现:


image

clearDeallocating() 的实现;
image

六、弱引用管理

image

1、添加 weak 变量:

image
一个 __weak 修饰的对象,系统会调用objc_initWeak()方法对其处理,最终会调用 weak_register_no_lock()方法对其进行具体操作,通过弱引用对象进行一个哈希运算查找到在对应弱引用表的位置,若此位置有弱引用数组则添加新的对象到数组中,若没有则重新创建弱引用地址,在第0个位置添加新的weak指针,后面的初始化为0或者nil

源码解析:


image
image
image
image
image
image

2、清除weak变量,同时设置指向为nil的过程:

image

源码解析


image
image
image
当一个对象进行dealloc操作,系统会调用 weak_clear_no_lock() 方法对其操作,具体是系统会通过哈希算法查找对应的弱引用表中的位置,若找到则返回一个数组,数组中存储所有的弱引用关系表,然后系统对数组进行遍历,把所有的弱引用指针分别指向nil

七、自动释放出

问题:

  • 请问array的内存是在什么时候释放的?
image
  • AutoreleasePool的实现原理?
  • AutoreleasePool为何可以嵌套使用?

1、AutoreleasePool

编译器会将@autoreleasepool{} 改写为


image

objc_autoreleasePoolPush:

image

objc_autoreleasePoolPop:

image

一次pop操作相当于一次批量的pop操作

2、AutoreleasePool的数据结构

  • 是以栈为结点双向链表的形式组合而成;
  • 是和线程一一对应的
双向链表
image

后入先出


image
AutoreleasePoolPage
image

原本的内存图:


image

发生push操作后:


image
[obj autorelease]的系统实现过程:
image

AutoreleasePoolPage::pop

  • 根据传入的哨兵对象找到对应的文职
  • 给上次 push 操作之后添加的对象依次发送 release 消息
  • 回退到 next 指针的正确位置

自动释放池总结

  • 在当次的 RunLoop 将要结束的时候调用 AutoreleasePoolPage::pop();
  • 多层嵌套就是多次插入哨兵对象;
  • 在 for 循环中 alloc 图片数据等内存消耗较大的场景手动插入 AutoreleasePool,降低内存的峰值(使用场景)

八、循环引用

三种循环引用:

  • 自循环引用
  • 相互循环引用
  • 多循环引用

1、自循环引用

image

2、相互循环引用

image

3、多循环引用

image

注意点:

  • 代理
  • block
  • NSTimer
  • 大循环引用

如何破除循环引用?

  • 避免产生循环引用(如使用弱引用修饰)
  • 在合适的时机断掉循环引用

具体解决方案

  • __weak
  • __block
  • __unsafe_unretained

1、__weak 破解

image

2、__block 破解

注意:

  • MRC 下,__block 修饰的对象不会增加其引用计数,避免了循环引用
  • ARC 下,__block 修饰的对象会被强引用,无法避免循环引用,需手动解决

3、__unsafe_unretained 破解

  • 修饰对象不会增加其引用计数,避免循环引用
  • 如果被修饰对象在某一时机被释放,此时再次访问时,会产生悬垂指针,导致内存泄漏

NSTimer 的循环引用问题

1、若非循环 timer

可将 timer 设置无效,然后置空

2、循环 timer

设置中间对象,中间对象持有对 timer 和 VC 的弱引用变量。NSTimer 分派的回调是在中间变量中实现。在中间变量回调的方法中对其所持有的 target 进行判断,若当前值存在则将NSTimer的值回调给原对象;若不存在(已经被释放),则设置timer为无效状态,具体代码实现如下:


image

(修改系统方法实现)


image

(完成改写)
image

总结

  • 什么是 ARC ?
  • 为什么 weak 指针指向的对象在被废弃之后会被自动置为 nil ?
  • 苹果是如何实现 AutoreleasePool 的?
  • 什么是循环引用?有哪些循环引用?怎样解决?

你可能感兴趣的:(内存管理)