iPhone 作为一个移动设备,其计算和内存资源通常是非常有限的,而许多用户对应用的性能却很敏感,卡顿、应用回到前台丢失状态、甚至 OOM 闪退,这就给了 iOS 工程师一个很大的挑战。
网上的绝大多数关于 iOS 内存管理的文章,大多是围绕 ARC/MRC、循环引用的原理或者是如何找寻内存泄漏来展开的,而这些内容更准确的说应该是 ObjC 或者 Swift 的内存管理,是语言层面带来的特性,而不是操作系统本身的内存管理。
如果我们需要聊聊”管理“内存,那么就需要先了解一些基础知识。
内存基础概念复习
物理内存
一个设备的 RAM 大小。以下是维基百科上的资料:
简单来说,iPhone 8(不包括 plus) 和 iPhone 7(不包括 plus)及之前都是 2G 内存,iPhone 6 和 6 plus 及之前都是 1G 内存。
虚拟内存(VM for Virtual Memory)
每个进程都有一个自己私有的虚拟内存空间。对于32位设备来说是 4GB,而64位设备(5s以后的设备)是 18EB(1EB = 1000PB, 1PB = 1000TB),映射到物理内存空间。
页
内存管理、映射中的基本单位是页,一页的大小是 4kb(早期设备)或者 16kb(A7 芯片及以后)
因为有页的存在,每次申请内存都必须以页为单位。然而这样一来,如果只是申请几个 byte,却不得不分配一页(16kb),是非常大的浪费。因此在用户态我们有 “heap” 的概念。
Page In/Out
由于虚拟内存的空间远远大于物理内存,在任意一个时间点,虚拟内存中的一个页并不一定总是在物理内存中,而是可能被暂时存到了磁盘上,这样物理内存便可以暂时释放这部分空间,供优先级更高的任务使用,因此磁盘可以作为 backing store 以扩展物理内存(MacOS 中有,iOS 没有)。另一种可能是加载一个比较大的文件/动态库,每次使用我们可能只需要加载其中的一部分,那么就可以使用 mmap 映射这个文件到虚拟内存空间,这样当我们访问其中一部分时,系统会自动把这一部分从磁盘加载到内存,而不加载其余部分。
这样把磁盘中的数据写到内存/从内存中写回磁盘成为 page in/out。
Wired memory
无法被 page out 的内存,主要为系统层所用,开发者不需要考虑这些。
VM Region
一个 VM Region 是指一段连续的内存页(在虚拟地址空间里),这些页拥有相同的属性(如读写权限、是否是 wired,也就是是否能被 page out)。举几个例子:
-
mapped file,即映射到磁盘的一个文件
-
__TEXT,r-x,多数为二进制
-
__DATA,rw-,为可读写数据
-
MALLOC_(SIZE),顾名思义是 malloc 申请的内存
VM Object
每个 VM Region 对应一个数据结构,名为 VM Object。Object 会记录这个 Region 内存的属性
Resident Page
当前正在物理内存中的页(没有被交换出去)
与其他App共存的情况
-
app 内存消耗较低,同时其他 app 也非常“自律”,不会大手大脚地消耗内存,那么即使切换到其他应用,我们自己的 app 依然是“活着”的,保留了用户的使用状态,体验较好
-
app 内存消耗较低,但是其他 app 非常消耗内存(可能是使用不当,也可能是本身就非常消耗内存,比如大型游戏)