内存管理——内存布局、TaggedPointer

184 阅读3分钟

内存管理

说到iOS的内存管理,不仅仅是MRC、ARC这些大众所熟知的,还有一些内存高效管理的代表之一TaggedPointer

内存布局

首先来了解下 我们的内存布局总共分为五大区:

  1. 栈区
  2. 堆区
  3. 全局区(静态区.bss)
  4. 常量区(.data)
  5. 代码区(.text)

栈区 :

  • 由编译器分配和释放,主要存储局部变量、方法参数、方法指针、函数等

  • 内存地址:一般为0x7开头

  • 定位方式:栈区的内存是通过sp寄存器直接去定位,所以查找速度很快

  • 内存区域是连续的,所以大小有限制,但是不会产生内存碎片,安全可靠

image.png image.png

堆区 :

  • 由运行时分配内存,主要存储需要开辟空间存储的,比如alloc、new、block的copy等

  • 内存地址:一般为0x6开头

  • 定位方式:堆区的内存是先通过寄存器定位到包含内存空间的地址,然后通过地址定位到内存的位置,所以它相对栈区多了一步操作过程,速度也就不如栈区了

  • 内存区域不连续,容易产生内存碎片,所以需要注意野指针、内存泄漏等问题

image.png image.png

全局区.bss :

  • 主要存储未初始化的全局变量、静态变量
  • 内存地址:一般为0x1开头

image.png image.png image.png

常量区 .data :

  • 主要存储初始化的全局变量、静态变量
  • 内存地址:一般为0x1开头 image.png image.png image.png

代码段 .text :

  • 主要存储程序运行的二进制代码

TaggedPointer

又称为小对象,主要是一些类型NSNumber、NSDate、NSIndexPath、Colors等等

首先来看一个普通对象指针结构,将它分解为二进制表示法

image.png

我们有64位,然而并没有真正的使用到所有这些位

image.png 一个真正的对象只有使用到中间的这些位,由于内存对齐要求的存在,右边的低位始终为0;而由于地址空间有限,左边的高位也始终为0;我们实际上不会用到2^64

因为高位和低位始终为0,所以我们从这些始终为0的位中选择一个位让它变成1,这可以让我们知道这不是一个真正的对象指针,这样可以将其他位赋予一些别的意义

这样的指针就称为TaggedPointer

iOS13之前分布的布局 image.png

这里为什么使用高位的1来区分TaggedPointer呢,实际是对msg_send的一个优化,为了通过一次比较来对taggedPointer和nil进行检查

iOS14之后的优化 image.png

优化后将tag标签移到低位,中间的有效负载成为正常的指针,这样和我们普通对象指针结构很相似。因为动态链接会忽略指针的前八位,这是由于一个Top Byte Ignore 的ARM特性

这样的作用是,它开启了TaggedPointer引用二进制文件中常量数据的能力(例如字符串或其他数据结构),否则他们将占用dirty memory(这里的内存很宝贵,后期会单出一篇来讲)

image.png

TaggedPointer指针的值不再是地址,而是真正的值,实际上它不是一个对象,而是一个披着对象皮的普通变量,而且它不需要malloc和free,在内存上读取有着3倍的效率,创建比之前快106倍

下面一个测试

image.png

image.png

image.png 不断的读写操作造成崩溃

image.png 当将nameStr改成test时则正常运行不会崩溃,因为这里已经变成TaggedPointer类型了

字符串长度在10以内都是TaggedPointer小对象类型,大于10是对象类型

image.png 它不受retain release ARC操作影响,可以看到它的强大之处了