这篇文章依次介绍一下iOS中内存的分配与分区。
1、RAM & ROM
- 存储器(Memory)是计算机系统中的记忆设备,用来存放程序和数据。
- iOS的存储器包括RAM(random access memory,运行内存)和ROM(Read-Only Memory,只读存储器)。
- RAM:运行内存,不能掉电存储。
- ROM:存储性内存,可以掉电存储,例如内存卡、Flash。
- 由于 RAM 类型不具备掉电存储能力(即一掉电数据消失),所以 app 程序一般存放于 ROM 中。RAM 的访问速度要远高于 ROM,价格也要高。
2、App程序启动
- App 程序启动,系统会把开启的那个 App 程序从 Flash 或 ROM 里面拷贝到内存(RAM),然后从内存里面执行代码。
- 注意CPU 不能直接从内存卡里面读取指令!(需要 Flash 驱动)
3、iOS内存分区
- iOS应用在内存(RAM)中运行,不同的类型在内存处于不同分区
- 图中由进程箭头指向的代码段、全局数据段是在进程就绪状态时通过编译器装载进来的
- 堆区、栈区、内存映射区域是在进程进入执行状态(即运行时)后向CPU(处理器)申请分配
- 进程结束这些区域会随着销毁,保留区和内核区除外
- 堆和栈是可增长的,它们的增长方向相对,堆向上增长,栈向下增长
下面解释一下每个区域:
保留段
- 系统分配出来的一块空闲区域,用来处理紧急情况下的任务
代码段
- 储存编译完成之后的指令代码(可执行代码的二进制文件)
- 在程序编译时存入,程序结束后由系统释放
- 通常代码段是可共享的,这使得需要频繁被执行的程序只需要在内存中拥有一份拷贝即可
- 代码段也通常是只读的,这样可以防止其他程序意外地修改其指令
- 在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等
常量区
- 存放常量字符串
- 常量区的内存在编译阶段完成分配,程序运行时会一直存在内存中,只有当程序结束后才会由操作系统释放
- iOS中常量区的地址空间一般以0x1开头
全局数据段(全局区/静态区)
- static修饰的变量成为静态变量,该变量内存放在全局数据段
- 由程序编译时创建,已初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量存放在另一块区域,直到程序结束才释放
- iOS中全局数据段的地址空间一般以0x1开头
未初始化数据(Block Started by Symbol)
- 简称
BSS ,存放程序中未初始化的全局变量、静态变量等数据 - 如
int a ,并未赋值的变量a就被认为是未初始化数据
已初始化数据(data segment)
- 简称
DATA ,存放程序中已初始化的全局变量、静态变量等数据 - 如
int a = 10 ,NSString *strS = @"123456789" 等
堆区(heap)
- 堆一般用来存储引用类型的对象数据
- 堆从低地址向高地址扩展数据,采用链表形式管理内存段:需要分配时对链表进行遍历,找到空间大于申请的节点分配给开发者,然后在链表中删除节点。找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中
- 变量使用结束后,需要由开发者根据引用计数来判断是否释放(ARC下由编译器自动添加释放命令),不释放可能引起内存泄露
- 堆区的内存是应用程序共享的,堆中的内存分配是系统负责的
- iOS中堆的地址空间一般以0x6开头,其空间总是在程序运行时动态分配
- 堆由于是运行时开辟、且内存区域是不连续的,导致容易出现内存碎片
内存映射段
- mmap文件映射和匿名映射:磁盘文件的数据映射到内存,通过修改内存就能修改磁盘文件
- 该段可以理解为用来处理有关文件映射的内容
栈区(stack)
- 存放程序临时创建的局部变量,也就是说我们函数括弧“{}” 中除了 static 定义之外的变量(static 变量存放在数据段),包括函数的参数以及隐藏参数
(id self, SEL _cmd) - 栈从高地址向低地址扩展,是一块连续的内存区域,遵循先进后出原则,一旦出了作用域就会被销毁
- 栈是系统数据结构,栈是在运行时开辟,其对应的线程或进程是唯一的
- 程序员不需要管理栈区变量的内存,由编译器自动分配释放
- iOS中栈的地址空间一般以0x7开头
- 可用于函数调用的现场保护和现场恢复
- 栈的内存大小有限制。根据苹果线程编程指南,iOS的主线程栈大小不超过1MB,OS X主线程栈最大8MB,子线程栈最大512KB。子线程在创建的时候可以更改栈的大小,子线程允许设置的最小栈大小为16 KB,并且栈大小必须为4 KB的倍数。主线程的栈大小无法修改。
内核区
- 系统内核运行的区域
- iOS的核心是XNU内核;XNU内核是一个混合内核,其核心是一个叫Mach的微内核
一般说的五大分区
- 堆区、栈区、全局区、常量区、代码区
- 一段代码看出变量在哪个区
int i = 10;
char *a;
- (void)test {
int b;
NSString *str = @"123";
static int c = 0;
NSObject *obj = [[NSObject alloc]init];
}
其它操作系统
- iOS 是基于 UNIX、Android 是基于 Linux 的,在 Linux 和 unix 系统中,内存管理的方式基本相同,Android 应用程序的内存分配也是如此
- 这些应用层的程序使用的都是虚拟内存,它们都是建立在操作系统之上的,只有开发底层驱动或板级支持包时才会接触到物理内存。在嵌入式 Linux 中,实际的物理地址只有 64M 甚至更小,但是虚拟内存却可以高达 4G
注意事项
- 当一个 app 启动后,代码区、常量区、全局区大小已固定,因此指向这些区的指针不会产生崩溃性的错误。而堆区和栈区是时时刻刻变化的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这两个区里面的内存时,一定要注意内存是否已经被释放,否则会产生程序崩溃(野指针报错等)。
|