iOS虚拟内存与内存分页

虚拟内存

虚拟内存是计算机系统内存管理的一种技术,虚拟内存为每个进程提供了一个连续、私有的地址空间,它每个进程会认为自己在使用一块大的连续的内存。事实上,每个进程的内存散布在物理内存的不同区域。或者可能被调出到备份存储中(一般在硬盘)。当一个进程请求自己的内存,操作系统负责把程序生成的虚拟地址,映射到实际存储的物理内存上。操作系统在分页表中存储虚拟地址到物理地址的映射。

OS X和iOS操作系统都默认支持虚拟内存。每个进程都拥有相同的虚拟内存空间,32位进程拥有4 GB的可寻址空间。此外,OS X为64位进程提供了大约18艾字节的可寻址空间。只有开始使用申请到的虚拟内存时,系统才会将虚拟地址映射到物理地址上,从而让程序使用真实的物理内存。

上述可以了解到虚拟内存的寻址空间是比物理内存还要大的。操作系统还会在内存不够的情况下,将某一进程的内存全部放入硬盘空间中,并在切换到该进程时再从硬盘读取出来。

虚拟内存优势

  • 为每个进程定义了一个连续的虚拟地址空间,用来映射在物理内存上不连续的内存区域;

  • 每个进程可以使用获得大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页保存到硬盘中。

  • 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。

寻址

现代处理器使用的是一种称为 虚拟寻址(Virtual Addressing) 的寻址方式。使用虚拟寻址,CPU需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。 实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有一个被称为 内存管理单元(Memory Management Unit, MMU)的硬件。MMU 需要借助存放在内存中的页表来动态翻译虚拟地址,该页表由操作系统管理。

内存分页

虚拟内存管理器对虚拟内存地址,划分为大小统一的内存块,称为 内存页,处理器及其内存管理单元(MMU)维护一个页表,此页表是程序的逻辑地址对计算机RAM中的物理地址的映射。当程序代码访问内存中的地址时,MMU使用页表将指定的逻辑地址转换为实际的硬件内存地址。

系统将内存页分为三种状态。

活跃内存页(active pages)- 这种内存页已经被映射到物理内存中,而且近期被访问过,处于活跃状态。
非活跃内存页(inactive pages)- 这种内存页已经被映射到物理内存中,但是近期没有被访问过。
可用的内存页(free pages)- 没有关联到虚拟内存页的物理内存页集合。
当可用的内存页降低到一定的阀值时,系统就会采取低内存应对措施,在OSX中,系统会将非活跃内存页交换到硬盘上,而在iOS中,则会触发Memory Warning,如果你的App没有处理低内存警告并且还在后台占用太多内存,则有可能被杀掉。

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 内存的属性

Page fault

页缺失指的是当软件试图访问已映射在虚拟地址空间中,但是目前并未被加载在物理内存中的一个分页时,由内存管理单元MMU所发出的中断。实际上, 这并不是什么"错误"。 一个程序进程可能占几Mb内存, 但并不是所有的指令都要同时运行,所以系统不会把所有的指令都从磁盘加载到page内存,那么当cpu在执行指令时, 如果发现下一条要执行的指令不在实际的物理内存page中时, CPU 就会 生成一个 page fault, 通知MMU把下面要执行的指令从磁盘加载到物理内存page中。

把磁盘中的数据写到内存的过程是Page in
把内存中的数据写到磁盘的过程是Page out。

mmap

mmap()是在 中定义的一个函数,此函数的作用将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

通过上面 mmap 与 文件标准IO 的对比

优势:

  • 物理内存占用延后:数据直到真正被使用时才会发生拷贝。
  • 物理内存占用减少:对于同一份文件无需在物理内存中存放两份,且文件区被划分成片,缺页异常时只将所需的页拷贝到物理内存。
  • 方便实现跨进程数据交互、共享:当映射到虚拟内存的对象被设置为共享对象,则不同进程对映射对象的写操作相互可见。

劣势:

  • 无法映射变长文件:调用mmap()时需指定要映射的文件位置和需要映射的大小范围。
  • 如果需要映射的文件过大,会导致过度占用虚拟内存:在调用mmap()后,虚拟内存空间就创建了,此时虽然不会占用物理内存,但依然会占用虚拟内存。此时可考虑只映射文件中自己需要的部分。

场景

当我们需要访问一个比较大的文件,尤其是当我们只需要访问其中的一小部分数据的时候,我们可以尝试通过 mmap 的方式来进行访问,这时候只会创建对应的虚拟内存空间,加载需要的部分的时候才会load到物理内存中,这样就可以避免由于该文件过大,使用标准文件IO对物理内存的过度占用的问题。

Instruments Allocations

使用Instruments的Allocations工具可以查看当前App说使用的内存,

总的内存占用 = All Heap Allocations + All Anonymous VM


image.png
  • All Heap Allocations,几乎所有类实例,包括 UIViewController、UIView、UIImage、Foundation 和我们代码里的各种类/结构实例。一般和我们的代码直接相关。
  • All Anonymous VM,可以看到都是由”VM:”开头的


    image.png

主要包含一些系统模块的内存占用。有些部分虽然看起来离我们的业务逻辑比较远,但其实是保证我们代码正常运行不可或缺的部分,也是我们常常忽视的部分。一般包括:

  • CG raster data(光栅化数据,也就是像素数据。注意不一定是图片,一块显示缓存里也可能是文字或者其他内容。通常每像素消耗 4 个字节)
  • Image IO(图片编解码缓存)
  • Stack(每个线程都会需要500KB左右的栈空间)
  • CoreAnimation
  • SQLite
  • network
  • 等等

总结

虚拟内存是对内存的一个抽象。支持虚拟内存的CPU需要通过虚拟寻址的方式来引用内存中的数据。CPU加载一个虚拟地址,然后发送给MMU进行地址翻译,MMU借助页表来获得物理地址。

参考
https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/AboutMemory.html
https://juejin.cn/post/6844904058667401230
https://zhuanlan.zhihu.com/p/49829766

你可能感兴趣的:(iOS虚拟内存与内存分页)