iOS底层系统:虚拟内存

4,512 阅读5分钟

关于作者

E-moss,程序员,爱好阅读和撸狗,主要从事iOS开发工作,公众号:知本集。  
主要分享和编写技术方面文章,不定期分享读书笔记,亦可访问“知本集”Git地址:https://github.com/knowtheroot/KnowTheRoot_iOS,欢迎提出问题和讨论。

Git地址:github.com/knowtheroot…

一、什么是虚拟内存

1.申请内存

当我们向系统申请内存时,系统并不会直接返回物理内存的地址,而是返回一个虚拟内存地址。从系统角度来说,每一个进程都有相同大小的虚拟内存空间。
只有当进程开始使用申请到的虚拟内存时,系统才会将虚拟地址映射到物理地址上,从而让程序使用真实的物理内存。

2.内存不够怎么办?

当A进程占用了大部分内存,此时B进程需要内存时发现内存不足,系统则会通知App,让App清理内存,既我们熟知的Memory Warning。

3.虚拟内存的缺点

虚拟内存也有同样的缺点:硬盘的容量比内存大,但也只是相对的,速度却非常缓慢,如果和硬盘之间的数据交换过于频繁,处理速度就会下降,表面上看起来就像卡住了一样,这种现象称为抖动(Thrushing)。相信很多人都有过计算机停止响应的经历,而造成死机的主要原因之一就是抖动。

二、内存分页

iOS系统会对虚拟内存和物理内存进行分页,虚拟内存到物理内存的映射都是以页为最小粒度的

1.什么是分页

1.1 分页的核心

将虚拟内存空间和物理内存空间皆划分为大小相同的页面,如4kb、8kb或16kb等。并以页面作为内存空间的最小单位,一个程序的一个页面可以存放在任意一个物理页面里。

1.2 分页解决了什么问题

  • 解决空间浪费碎片化问题:由于将虚拟内存空间和物理内存空间按照某种规定的大小进行分配,这里我们称之为页(Page),然后按照页进行内存分配,也就克服了外部碎片的问题。
  • 解决程序大小受限问题:将当前需要的页面放在内存里,其他暂时不用的页面放在磁盘上,这样一个程序同时占用内存和磁盘,其增长空间就大大增加了。

1.3 内存分页的状态

系统将内存页分为三个状态:
(1)活跃的内存页(active page):内存页已经被映射到物理内存中,而且近期被访问过,处于活跃状态。
(2)非活跃的内存页(inactive page):内存页已经被映射到物理内存中,但是近期没有被访问过。
(3)可用的内存页(free page):没有关联到虚拟内存页的物理内存页集合。

当可用的内存页降低到一定的阀值时,系统就会采取低内存应对措施,在OSX中,系统会将非活跃内存页交换到硬盘上,而在iOS中,则会触发Memory Warning,如果你的App没有处理低内存警告并且还在后台占用太多内存,则有可能被杀掉。

1.4 页表

页表的功能是提供虚拟页面到物理页面的映射
页表的记录条数与虚拟页面数相同。此外,内存管理单元依赖于页表来进行一切与页面有关的管理活动,这些活动包括判断某一页面号是否在内存里,页面是否受到保护,页面是否非法空间等等。
由于页表的特殊地位,决定了它是由硬件直接提供支持,即页表是一个硬件数据结构

三、VMObject

为了更好的管理内存页,系统将一组连续的内存页关联到一个VMObject上。
VMObject主要包含以下属性:

  • Resident pages - 已经被映射到物理内存的虚拟内存页列表
  • Size - 所有内存页所占区域的大小
  • Pager - 用来处理内存页在硬盘和物理内存中交换问题
  • Attributes - 这块内存区域的属性,比如读写的权限控制
  • Shadow - 用作(copy-on-write)写时拷贝的优化
  • Copy - 用作(copy-on-write)写时拷贝的优化

四、虚拟内存和堆(heap)

堆区会被划分成很多不同的VM Region,不同类型的内存分配根据需求进入不同的VM Region。

五、malloc和calloc

OC中,除了使用NSObject的alloc分配内存外,还可以使用c的函数malloc进行内存分配。malloc的内存分配当然也是先分配虚拟内存,然后使用的时候再映射到物理内存,不过malloc有一个缺陷,必须配合memset将内存区中所有的值设置为0。这样就导致了一个问题,malloc出一块内存区域时,系统并没有分配物理内存。然而,调用memset后,系统将会把malloc出的所有虚拟内存关联到物理内存上,因为你访问了所有内存区域。

为了解决这个问题,苹果官方推荐使用calloc代替malloc,calloc返回的内存区域会自动清零,而且只有使用时才会关联到物理内存并清零。

主要区别

calloc和malloc 主要的区别在于前者在返回内存的指针之前将它初始化为0,另外它们请求数量的方式不同。calloc的参数包括所需元素的数量和每个元素的字节,根据这些值可以计算出总共需要分配的内存空间(num_elements*element_size)个字节。