iPhone 做为一个移动设备,其计算和内存资源一般是很是有限的,而许多用户对应用的性能却很敏感,卡顿、应用回到前台丢失状态、甚至 OOM 闪退,这就给了 iOS 工程师一个很大的挑战。html
网上的绝大多数关于 iOS 内存管理的文章,大可能是围绕 ARC/MRC、循环引用的原理或者是如何找寻内存泄漏来展开的,而这些内容更准确的说应该是 ObjC 或者 Swift 的内存管理,是语言层面带来的特性,而不是操做系统自己的内存管理。git
若是咱们须要聊聊”管理“内存,那么就须要先了解一些基础知识。github
一个设备的 RAM 大小。如下是维基百科上的资料:web
简单来讲,iPhone 8(不包括 plus) 和 iPhone 7(不包括 plus)及以前都是 2G 内存,iPhone 6 和 6 plus 及以前都是 1G 内存。面试
每一个进程都有一个本身私有的虚拟内存空间。对于32位设备来讲是 4GB,而64位设备(5s之后的设备)是 18EB(1EB = 1000PB, 1PB = 1000TB),映射到物理内存空间。缓存
内存管理、映射中的基本单位是页,一页的大小是 4kb(早期设备)或者 16kb(A7 芯片及之后)网络
由于有页的存在,每次申请内存都必须以页为单位。然而这样一来,若是只是申请几个 byte,却不得不分配一页(16kb),是很是大的浪费。所以在用户态咱们有 “heap” 的概念。session
因为虚拟内存的空间远远大于物理内存,在任意一个时间点,虚拟内存中的一个页并不必定老是在物理内存中,而是可能被暂时存到了磁盘上,这样物理内存即可以暂时释放这部分空间,供优先级更高的任务使用,所以磁盘能够做为 backing store 以扩展物理内存(MacOS 中有,iOS 没有)。另外一种多是加载一个比较大的文件/动态库,每次使用咱们可能只须要加载其中的一部分,那么就可使用 mmap 映射这个文件到虚拟内存空间,这样当咱们访问其中一部分时,系统会自动把这一部分从磁盘加载到内存,而不加载其他部分。数据结构
这样把磁盘中的数据写到内存/从内存中写回磁盘成为 page in/out。架构
没法被 page out 的内存,主要为系统层所用,开发者不须要考虑这些。
一个 VM Region 是指一段连续的内存页(在虚拟地址空间里),这些页拥有相同的属性(如读写权限、是不是 wired,也就是是否能被 page out)。举几个例子:
每一个 VM Region 对应一个数据结构,名为 VM Object。Object 会记录这个 Region 内存的属性
当前正在物理内存中的页(没有被交换出去)
在 iOS 上管理杀进程释放资源策略模块叫作 Jetsam,这里推荐五子棋的文章,其中有详细的介绍。
苹果官方关于 OOM 的文档和接口很是少,以致于 facebook 在判断应用是否上次由于 OOM 而闪退时,须要通过一个漫长的逻辑判断,当不知足全部条件时才能断定为 OOM(想象一下若是系统能提供一个接口,告诉开发者上次的退出缘由,会方便多少!)
咱们会好奇,当一个普通 app 启动时,内存消耗究竟有多少?
咱们刚讨论到内存的不一样类别,那么应该选用哪一个值做为内存占用量的标准呢?
在 WWDC13 704 中,苹果推荐用 footprint 命令来查看一个进程的内存占用。
关于什么是 footprint,在官方文档 Minimizing your app’s Memory Footprint 里有说明:
Refers to the total current amount of system memory that is allocated to your app.
因为该命令只能在 MacOS 上运行,而且 iOS 上也没有 Activity Monitor,咱们新建一个 Mac app,而后用不一样手段测量内存占用
能够看到,Xcode、系统、footprint 工具和 phys_footprint 获得的数据是一致的,而既然官方推荐了 footprint,所以咱们以这几个方法获得的结果做为标准。猜想 footprint 比 Instruments 数据更大的缘由是存在一些”非代码执行开销“,如把系统和应用二进制加载进内存。iOS 中虽然不能使用系统 Activity Monitor 和 footprint 命令,也能在 Xcode 中和 phys_footprint 获得一样的结果。
至此咱们能够获得一个结论: Instruments 中显示的部分,其实也只是整个应用进程里内存的一部分。可是因为咱们可以控制的只有这一部分,所以应该把精力投入到 Instruments 的分析中去。
应用的详细性能分析老是须要依赖 Instruments 的强大功能。从 Allocations 角度来看,总的内存占用 = All Heap Allocations + All Anonymous VM:
主要包含一些系统模块的内存占用。有些部分虽然看起来离咱们的业务逻辑比较远,但实际上是保证咱们代码正常运行不可或缺的部分,也是咱们经常忽视的部分。通常包括:
咱们平时最常常会作的 debug 之一,就是查找循环引用。而循环引用形成的 leak 数据一般是 UIKit 或咱们本身的一些数据结构,会被归类到 heap。这些是咱们相对熟悉的,网上也有很是多的文章,这里再也不讨论。而就 VM 这块来讲,由于不受咱们直接控制,文档也较少,因此相对神秘一些,每每容易被忽视。
对于 VM 中的线程栈开销、网络 buffer 等,咱们其实没有太大的控制能力,一般这些也不会是内存开销的主要缘由(除非有成百上千的线程和频繁大量的网络请求)。而对于即刻和绝大多数 app 来讲,尤为是采用了 AsyncDisplayKit(用空间换时间)的状况下,渲染开销是绝对不可忽视的一块。
我一直认为,移动设备上无论是 CPU、GPU 仍是内存,最大的性能杀手必定是布局和渲染。布局数据和通常数据结构相似,单个内存开销最多以 KB 计,而渲染缓存很容易就用“兆”来计算,更容易影响到总体开销。
任意打开一个 app,能够看到渲染无非就是两大部分:图片和文字。
咱们知道,解压后的图片是由无数像素数据组成。每一个像素点一般包括红、绿、蓝和 alpha 数据,每一个值都是 8 位(0–255),所以一个像素一般会占用 4 个字节(32 bit per pixel。少数专业的 app 可能会用更大的空间来表示色深,消耗的内存会相应线性增长)。
下面咱们来计算一些一般的图片开销:
有了大体的概念,之后看到一张图能简单预估,大概会吃掉多少内存。
图片解码是每一个开发者都绕不过去的话题。图片从压缩的格式化数据变成像素数据须要通过解码,而解码对 CPU 和内存的开销都比较大,同时解码后的数据如何管理,如何显示都是须要咱们注意的。
Share hardware-accelerated buffer data (framebuffers and textures) across multiple processes. Manage image memory more efficiently.
网上关于渲染的资料不少,可是不少都是人云亦云,咱们来讲一些比较少讨论的点:
做为 backing image 的 CGImage
Offscreen rendering is invoked whenever the combination of layer properties that have been specified mean that the layer cannot be drawn directly to the screen without pre- compositing. Offscreen rendering does not necessarily imply software drawing, but it means that the layer must first be rendered (either by the CPU or GPU) into an offscreen context before being displayed.
关于文字渲染的文档资料并非不少,所以咱们须要作一些实验来判断。 新建一个项目,添加一个全屏的 label,不停切换文字,获得 cpu 占用率稳定在 15%,gpu占用率 0%。而且 Time Profiling 显示
排名第一的方法主要是在调用 render_glyphs,说明主要是 CPU 参与了文字渲染。
理论上 iPhone X 全屏有 1125 * 2436 = 2740500 个像素,距离实际占用内存很是接近,只多了 143084 byte(139.73kb),说明差很少正好是一个像素对应一个字节。这印证了 WWDC(WWDC18 219和416)上的结论,即黑白的位图只占用 1 个字节,比 4 字节节省 75% 的空间。固然实际使用过程当中很难限制文字只采用黑白两种颜色,可是仍是应该了解苹果的优化过程。
在以上测试基础上,若是咱们尝试把第一个字符加上红色属性,或者添加 emoji,那么渲染结果就再也不是黑白的了,而是一张彩色图片,相似普通图片那样每一个像素须要 4 个字节来描述。所以理论上所消耗的内存会变成 2.75MB * 4 = 10MB 多一点。测试获得:
结果占用了 11468800 bytes,是原来 2740500 的 3.97 倍,与理论值 4 很是接近(可能内存中还存在一些附属的其余元数据,而这些不会如同像素数据同样线性放大,所以不彻底是精确 4 倍关系)。比较好的印证了以前的结论。
整整一屏的文字,在 3x 设备上,只占用了 2MB 多一点的内存,能够说是很是省了。
iOS 的内存管理有如下几个特色:
做者:即刻技术团队
连接:https://juejin.im/post/5bec0e...