OC语言是C语言的超集,所以先了解C语言的内存模型的内存管理会有很大帮助。C语言的内存模型分为5个区:栈区
、堆区
、全局/静态区
、常量区
、代码区
一般情况下程序存放在ROM
(只读内存,比如硬盘)或Flash
中,运行时需要拷到RAM
(随机存储器RAM)中执行,RAM
会分别存储不同的信息,如下图所示:
RAM(random access memory)
:运行内存,CPU可以直接访问,读写速度快,但是不能掉电存储。它又分为:
- 动态DRAM,速度慢一点,需要定期的刷新(充电),常说的内存条就是指它,价格会稍低一点,手机中的运行内存也是指它
- 静态SRAM,速度快,我们常说的一级缓存,二级缓存就是指它,价格高一点
ROM(read only memory)
:存储性内存,可以掉电存储,eg:SD卡、Flash(机械磁盘也可以简单的理解为ROM)。用的多的:NandFlash(空间大,便宜),还有NorFlash(直接运行程序,读取速度快)
|_|
由于RAM类型不具备掉电存储能力(即一停止供电数据全没了,从新上电后全是乱码,所以需要初始化),所以app程序一般存放于ROM中。RAM的访问速度要远高于ROM,价格也要高
由于RAM不能掉电存储,所以我们的APP程序,刷机包,下载的文件等等,都是在ROM里面存储的手机里面使用的ROM基本都是NandFlash,CPU是不能直接访问的,而是需要文件系统/驱动程序(嵌入式中的EMC)将其读到RAM里CPU才可以访问
下面先了解一下堆是怎么存放和操作数据的
堆是计算机科学中一类特殊的数据结构的统称。在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构
|_|
堆(Heap)又被为优先队列(priority queue)。尽管名为优先队列,但堆并不是队列。在堆中,我们不是按照元素进入队列的先后顺序取出元素的,而是按照元素的优先级取出元素
这就好像候机的时候,无论谁先到达候机厅,总是头等舱的乘客先登机,然后是商务舱的乘客,最后是经济舱的乘客。每个乘客都有头等舱、商务舱、经济舱三种个键值(key
)中的一个。头等舱->商务舱->经济舱
依次享有从高到低的优先级
总的来说,堆是一种数据结构,数据的插入和删除是根据优先级定的,他有几个特性:
≥
它的子节点≤
它的子节点举个例子,就像叠罗汉,体重大(优先级低、值大)的站在最下面,体重小的站在最上面(优先级高,值小)。 为了让堆稳固,我们每次都让最上面的参与者退出堆,也就是每次取出优先级最高的元素
栈是一块连续的内存区域
从高地址向低地址
进行存储,遵循先进后出(FILO)原则
栈区由操作系统在运行时自动分配,声明的变量过了作用域范围后内存便会自动释放,静态分配由编译器完成(自动变量auto
),动态分配由alloc
函数完成
函数内部定义的局部变量
、方法参数
(方法中的默认参数:self
、_cmd
),都存放在栈区
优缺点
快速高效
(栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放)主线程栈
大小是1MB,其他线程是512KB,MAC只有8M栈的地址空间在iOS中是以0x7
/0x16
开头
- (void)testStack { int a = 7777777; NSLog(@"a == %p, size == %lu", &a, sizeof(a)); NSLog(@"方法参数self:%p", &self); NSLog(@"方法参数cmd:%p", &_cmd); }
堆是不连续的内存区域从从低地址向高地址进行存储,类似于链表结构(便于增删,不便于查询),遵循先进先出(FIFO)原则
开发者需要关注变量的生命周期,如果不及时释放,会造成内存泄漏,只有等程序结束时由操作系统统一回收
存放的是OC中运行时使用alloc
、new
创建的对象,C/C++中使用malloc
、calloc
以及realloc
分配的空间,需要free
释放
优缺点
new
/delete
势必会造成内存空间的不连续性,速度慢、容易产生内存碎片(堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定,并不是一旦成为孤儿对象就能被回收)堆的地址空间在iOS中是以0x6
开头
- (void)testHeap { NSObject* object1 = [[NSObject alloc] init]; NSObject* object2 = [[NSObject alloc] init]; NSLog(@"object1 = %@", object1); NSLog(@"object2 = %@", object2); }
该区是编译时分配的内存空间,在程序运行过程中,此内存中的数据一直存在,全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放
未初始化的全局变量和静态变量,即BSS区(.bss
)
已初始化的全局变量和静态变量,即数据区(.data
)
全局区的空间地址在iOS中一般以0x1
开头
int bssA; static double dataB = 7.0; - (void)testExOrSt { NSLog(@"bssA == %p", &bssA); NSLog(@"dataB == %p", &dataB); }
整型
、字符型
、浮点
、常量字符串
等常量程序运行时的代码
会被编译成二进制,存进内存的代码区域函数栈:又称为栈区,在内存中从高地址往底地址分配,与堆区相对。
栈帧:指函数(运行中且未完成)占用的一块独立的连续内存区域
应用中新创建的每个线程都有专用的栈空间,栈可以在线程期间自由使用。而线程中有千千万万的函数调用,这些函数共享进程的这个栈空间。每个函数所使用的栈空间是一个栈帧,所有的栈帧就组成了这个线程完整的栈
函数调用是发生在栈上的,每个函数的相关信息(例如局部变量、调用记录等)都存储在一个栈帧中,每执行一次函数调用,就会生成一个与其相关的栈帧,然后将其栈帧压入函数栈,而当函数执行结束,则将此函数对应的栈帧出栈并释放掉
以下是ARM的栈帧布局方式
:
main stack frame
为调用函数的栈帧
func1 stack frame
为当前函数(被调用者)的栈帧
栈底
在高
地址,栈向下增长。FP
就是栈基址
,它指向函数的栈帧起始地址
SP
则是函数的栈指针
,它指向栈顶
的位置ARM压栈
的顺序
很是规矩(也比较容易被黑客攻破么),依次为当前函数指针PC
、返回指针LR
、栈指针SP
、栈基址FP
、传入参数个数及指针
、本地变量
和临时变量
。如果函数准备调用另一个函数,跳转之前临时变量区先要保存另一个函数的参数栈基址和栈指针明确标示栈帧的位置
,栈指针SP一直移动,ARM的特点
是,两个栈空间内的地址(SP+FP)前面,必然有两个代码地址(PC+LR)明确标示着调用函数位置内的某个地址
堆栈溢出
一般情况下应用程序是不需要考虑堆和栈的大小的,但是事实上堆和栈都不是无上限的,过多的递归会导致栈溢出,过多的alloc变量会导致堆溢出
所以预防堆栈溢出
的方法:
层次过深的递归
调用不要使用过多的局部变量
,控制局部变量的大小避免分配
占用空间太大的对象
,并及时释放
调用系统API
修改线程的堆栈大小
1. 各自的优缺点?
2. 申请后的系统如何响应?
3. 申请大小的限制?