iOS内存五大分区
总揽
iOS中,内存主要分为五大区域:栈区,堆区,全局区/静态区,常量区和代码区。总览图如下。
这个图我觉得更好记,因为下面是低地址,上面是高地址,是比较符合日常逻辑的。
由下到上开始背:代码区,常量区,全局/静态区,堆区(向上生长),栈区(向下生长),内核区
如上图所示,代码区是在低地址段存放,而栈区则存放在高地址段,并且各个分区之间不是连续的。
栈区
1.1 介绍
- 栈是从高地址向低地址存储的
一块连续的
内存区域,特点是先进后出(FILO)
- 栈的地址空间在iOS里面
0X7 / 0X16
开头 - 栈区一般是在
运行时分配内存,内存空间由系统管理
, 也就是变量超出了自身的作用范围之后就会被释放 - 包含
函数内部定义的局部变量以及方法参数(方法的默认参数self、cmd)
等也都是存放在栈区
1.2优缺点
- 注意,栈区的内存是由系统分配并管理的,所以它会由系统分配并自己释放,不会产生内存碎片,更快更高效。
- 但是栈的内存大小被系统所限制导致其并不是很灵活,
iOS主线程栈道大小1MB,其他线程512KB,Mac为8M
。
- (void)testStack {int a = 10;NSLog(@"a == %p size == %lu",&a,sizeof(a));NSLog(@"方法参数 self:%p",&self);NSLog(@"方法参数 cmd:%p",&_cmd);
}
可以看出,栈内存的分配是连续的参数如栈顺序为,self, _cmd, a。然后地址的大小变化为0x16b8cba28 -> 0x16b8cba20 -> 0x16b8cba1c (可以看出由高地址到低地址,一次递减8字节
堆区
- 堆是从低地址向高地址的
不连续的
内存区域,和链表的结构很相似(便于增删但不便于查询),特点是先进先出(FIFO)
- 堆地址是
以0x6开头
,动态的分配空间 - 在堆里面存放的东西需要我们手动的管理和释放,若不及时释放就会造成内存泄漏
- 在OC里面alloc和new都会为对象开辟空间到堆上
- (void)testHeap {NSObject *object1 = [NSObject new];NSObject *object2 = [NSObject new];NSLog(@"object1 = %@",object1);NSLog(@"object2 = %@",object2);
}
可以看出,堆内存分配是 不连续的
栈和堆的区别联系
- 各自的优缺点?
- 栈:由编译器自动分配并释放,速度较快,不会产生内存碎片。优点是快速高效,缺点是有限制,数据不灵活。
- 堆: 由程序员分配和释放,速度比较慢,而且容易产生内存碎片,不过用起来最方便。优点是灵活方便,数据适应面广泛,但是效率有一定降低
- 申请后的系统如何响应?
- 栈:存储每一个函数在执行的时候都会向操作系统索要资源,栈区就是函数运行时的内存,栈区中的变量由编译器负责分配和释放,内存随着函数的运行分配,随着函数的结束而释放,由系统自动完成。只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
- 堆:操作系统有一个记录空闲内存地址的链表。当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空间结点链表中删除,并将该结点的空间分配给程序。由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
- 申请大小的限制?
- 栈: 栈是向低地址扩展的数据结构,是一块连续的内存的区域。栈顶的地址和栈的最大容量是系统预先规定好的,栈的大小是2M(有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈道剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
- 堆: 堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
全局/静态区
- 该区是编译时分配的内存空间,在iOS中一般以0x1开头,在程序运行过程中,此内存中的数据一直存在,程序结束后由系统释放。
- 未初始化的全局变量和静态变量,即BSS区(.bss)。
- 已初始化的全局变量和静态变量,即数据区(.data)。
其中,全局变量是指变量值可以在运行时被动态修改,而静态变量是static修饰的变量,包含静态局部变量和静态全局变量
int clB;
static int bssB;
int initClB = 10;
static int initBssB = 11;
- (void)testStatic {NSLog(@"clA = %p", &clB);NSLog(@"bssB = %p", &bssB);NSLog(@"initClB = %p", &initClB);NSLog(@"initBssB = %p", &initBssB);
}
结论:
clB 和 bssB都是未初始化,在内存是连续的地址,相差为4。
initClB和 initBssB都是初始化的数据,内存地址也是连续的,也相差4。
常量区
- 该区是编译时分配的内存空间,在程序运行过程中,此内存中的数据一直存在,程序结束后由系统释放。
- 存放常量: 整形、字符型、浮点、字符串等
- 常量区是编译时分配的内存空间,在程序结束后由系统释放,主要存放:
- 已经使用了的,且没有指向的字符串常量
- 字符串常量因为可能在程序中被多次使用,所以在程序运行之前,就回提前分配内存。
代码区
- 该区时编译时分配的内存空间,在程序运行过程中,此内存中的数据一直存在,程序结束后由系统释放
- 程序运行时的代码会被编译成二进制,存进内存的代码区域
面试题
- 讲一下static关键字的各种情况以及作用
答:
- 对于全局变量来说,static 改变了其作用域。普通全局变量是所有文件都可以用。静态全局变量是只有当前文件可以用。
- 对于局部变量来说,static改变了其存储方式从而改变了生命周期。普通局部变量是动态存储,动态存储决定了其生命周期为变量使用期间。静态局部变量是静态存储,存储在全局静态区,生命周期为从程序开始道结束。
- 因此 static 这个说明符在不同的地方所起的作用是不同的。
- 总结:全局变量、静态全局变量、静态局部变量采用静态存储方式,局部变量采用动态存储方式。
更详细文章看这里:
iOS的static修饰符
- 问题1:请简述iOS应用程序的五大内存分区及其主要用途。
答案:
iOS应用程序的内存分为以下五个主要分区:
1. **栈(Stack)**:- **用途**:用于存储局部变量、函数参数、返回地址等。栈内存是自动分配和释放的,主要用于函数调用和局部变量的管理。- **特点**:内存分配方式为LIFO(后进先出),存取速度快,空间相对较小。2. **堆(Heap)**:- **用途**:用于动态分配内存,存储需要在运行时分配和释放的对象和数据。堆内存由程序员手动管理,通过`malloc`、`free`、 `new`、`delete`等函数进行分配和释放。- **特点**:内存管理灵活,存储空间较大,但分配和释放速度相对较慢,容易产生内存碎片。3. **全局区/静态区(Global/Static)**:- **用途**:存储全局变量和静态变量。全局变量在程序启动时分配,在程序结束时释放;静态变量在第一次使用时分配,程序结束时释放。- **特点**:内存地址固定,生命周期贯穿程序运行的整个周期。4. **常量区(Constant)**:- **用途**:存储常量数据,例如字符串常量、数值常量等。常量区的内容在程序运行时不可修改。- **特点**:只读区域,数据在程序加载时初始化,生命周期贯穿程序运行的整个周期。5. **代码区(Code/Text)**:- **用途**:存储程序的可执行代码,包括函数体和编译后的指令。代码区在程序运行时是只读的,以防止意外修改。- **特点**:只读区域,存储的是编译后的机器指令,生命周期贯穿程序运行的整个周期。
- 问题2:为什么栈内存的分配和释放速度比堆内存快?
答案:
1. 分配方式:栈内存采用LIFO(后进先出)的分配方式,每次函数调用时,函数的局部变量、参数和返回地址会依次入栈,函数返回时,这些数据会依次出栈。分配和释放只需要移动栈指针,操作简单且高效。
2. **内存管理**:栈内存由系统自动管理,函数调用结束时,系统会自动释放栈内存,无需程序员手动管理。堆内存则需要程序员手动管理,通过`malloc`、`free`等函数进行分配和释放,管理复杂且容易产生内存碎片。3. **空间连续**:栈内存通常是连续的内存块,分配和释放时不需要进行复杂的内存碎片整理,而堆内存由于频繁的分配和释放,容易产生内存碎片,导致分配和释放速度变慢。
- 问题3:什么是内存碎片?如何在iOS开发中避免内存碎片?
答案:
内存碎片是指由于频繁的内存分配和释放,导致堆内存中出现大量无法使用的小块空闲内存,从而降低内存利用效率和分配速度。
在iOS开发中,避免内存碎片的方法包括:
1. **使用自动内存管理**:iOS使用ARC(Automatic Reference Counting)来自动管理内存,减少手动分配和释放内存的操作,从而降低产生内存碎片的风险。2. **对象池技术**:对于频繁使用的对象,可以使用对象池(Object Pool)技术,将对象复用,而不是每次都创建新的对象,从而减少内存分配和释放的次数。3. **尽量避免频繁的内存分配和释放**:对于需要频繁分配和释放内存的操作,可以考虑优化算法或数据结构,减少内存分配和释放的频率。4. **使用合适的数据结构**:在设计数据结构时,尽量使用内存连续的数据结构,例如数组、链表等,避免过度使用需要频繁分配和释放内存的复杂数据结构。
- 问题4:全局区和静态区的内存是如何管理的?它们之间有什么区别?
答案:
全局区和静态区的内存管理方式如下:
- **全局区(Global)**:- 管理全局变量,即在程序的整个生命周期内都存在的变量。==这些变量在程序启动时分配内存==,在程序结束时释放内存。- 全局变量在定义时如果未显式初始化,系统会将其初始化为0。- **静态区(Static)**:- 管理静态变量,即在函数或类内部定义并带有`static`关键字的变量。==这些变量在第一次使用时分配内存==,在程序结束时释放内存。- 静态变量在第一次定义时如果未显式初始化,系统也会将其初始化为0。**区别**:
- **生命周期**:全局变量和静态变量的生命周期相似,都是在程序运行期间存在,但全局变量在程序启动时即被初始化,而静态变量在第一次使用时才被初始化。
- **作用域**:全局变量的作用域是整个程序,而静态变量的作用域仅限于其定义的函数或类内部。
- 问题5:代码区是只读的,这对程序安全性有什么影响?
答案:
代码区是只读的,这对程序的安全性有以下影响:
1. **防止代码篡改**:代码区的只读属性确保了程序在运行时,代码段不能被修改,从而防止恶意代码注入和篡改。这提高了程序的安全性,防止了病毒和恶意软件的攻击。2. **保护执行环境**:由于代码段是只读的,任何对代码区的写操作都会引发异常,从而防止意外或恶意修改代码指令,保证了程序的稳定运行。3. **避免缓冲区溢出攻击**:许多攻击技术,例如缓冲区溢出攻击,试图通过修改程序的执行路径来执行恶意代码。代码区的只读属性可以有效防止这些攻击,因为即使攻击者试图修改代码,系统也会检测到并阻止操作。