iOS 底层探索:内存五大区
前言
- 这篇开始探索内存管理,这篇主要介绍内存的分布的基础知识概念。
一 、 内存五大区
在iOS中,内存主要分为栈区、堆区、全局区、常量区、代码区五大区域。如下图所示
针对4GB运存栈区(Stack)
-
定义
-
栈是系统数据结构,其对应的进程或者线程是唯一的
-
栈是
从高地址向低地址
扩展扩展的数据结构 -
栈是一块连续的内存区域,遵循
先进后出(FILO)原则
-
栈的地址空间在iOS中是以0x7开头
-
栈区一般在运行时分配
-
-
存储
栈区是由编译器自动分配并释放的,主要用来存储
-
局部变量
-
函数的参数
,例如函数的隐藏参数(id self,SEL _cmd)
-
-
优缺点
-
优点:因为栈是由编译器自动分配并释放的,不会产生内存碎片,所以快速高效
-
缺点:栈的内存大小有限制,数据不灵活
-
注:
- iOS主线程栈大小是1MB
- 其他主线程是512KB
- MAC只有8M
缓冲区域
-
定义 : 栈区和堆区中间有小块未使用的内存区域。用于给栈区和堆区之间创建一个
缓冲区域
-
溢出
:到达缓冲区的数据向小缓冲区复制的过程中,由于没有注意小缓冲区的边界,导致小缓存区满了,从而覆盖了和小缓存区相邻内存区域的其他数据而引起的内存问题。(就像桶盛水,水多了,自然越界溢出来了。)
堆区(Heap)
- 定义
-
堆是从
低地址向高地址
扩展。 -
堆是不连续的内存区域,类似于链表结构(增删快,查找慢),遵循
先进先出(FIFO)原则
-
堆的地址空间在iOS中是以0x6开头,其空间的分配总是动态的
-
堆区的分配一般是在运行时分配
-
存储
- 堆区是由程序员动态分配和释放的,如果程序员不释放,程序结束后,可能由操作系统回收。
主要用于存放 :
a
.OC中使用alloc或者 使用new开辟空间创建对象;
b
.C语言中使用malloc、calloc、realloc分配的空间,需要free释放,释放对象在堆区的内存,并将栈中的地址指针置空
- 堆区是由程序员动态分配和释放的,如果程序员不释放,程序结束后,可能由操作系统回收。
-
优缺点
-
优点:灵活方便,数据适应面广泛
-
缺点:需手动管理,速度慢、容易产生内存碎片
-
注:
当需要访问堆中内存时,一般需要先通过对象读取到栈区的指针地址,然后通过指针地址访问堆区
野指针
:提前释放了,查询时找不到内容
内存泄露
:没有释放,一直占用内存
过度释放
:对已释放的对象进行release操作。
全局静态区(.bss)
- 存放全局变量和静态变量
- 空间由系统管理。(程序启动时,开辟空间;程序结束时,回收空间;程序执行期间一直存在)在iOS中一般以0x1开头;
- static修饰的变量仅执行一次,生命周期为整个程序运行期
常量区(.data)
- 存放常量(整型、字符型,浮点,字符串等),整个程序运行期不能被改变。
2 .空间由系统管理,生命周期为整个程序运行期。在iOS中一般以0x1开头。
代码区(即.text)
- 定义:代码区是编译时分配主要用于存放程序运行时的代码,代码会被编译成二进制存进内存的,
存放程序执行的CPU指令。(编译期将代码转换为CPU指令)
内核区:
- 交给系统处理的内核区域, 以4GB手机为例 ,图中高地址 0xc0000000 = 1024 x 1024 x 1024 x 3 总共占3GB,还有1GB就是留给内核区使用的。
- 0x0040000 ~ 0之间的保留
拓展
define
和const
区别:define: 宏。编译期不会进行语法识别,没有类型。编译期会分配内存。每次使用都会进行宏替换和开辟内存。
const: 常量。编译期会进行语法识别,需要指定类型。编译期不会分配内存,仅在第一次使用时,开辟内存并记录内存地址。后续调用时不会开辟内存,直接返回记录的内存地址。效率更快。内存占用更少。
内存验证
运行下面一段代码,看看变量在内存中是如何分配的
- (void)test {
NSInteger i = 666;
NSLog(@"NSInteger i -> 内存地址:%p", &i); // 【局部变量】 栈区
NSString * name = @"QWERDF";
NSLog(@"NSString name -> 内存地址: %p", name); // 【字符串内容】 存放在常量区
NSLog(@"NSString name -> 指针地址: %p", &name);// 【局部变量name的指针】 存放在栈区
NSObject * objc = [NSObject new];
NSLog(@"NSObject objc -> 内存地址: %p", objc);// 【对象的内容】 存放在堆区
NSLog(@"NSObject objc -> 指针地址: %p", &objc);//【对象的指针】 存放在栈区
}
打印结果: (0x7开头: 栈区 、 0x1开头: 常量区、 0x6开头: 堆区)
2020-11-02 11:19:39.121235+0800 001---- 内存验证[5104:87115] NSInteger i -> 内存地址:0x7ffeeb3e0668 (栈区)
2020-11-02 11:19:39.124740+0800 001---- 内存验证[5104:87115] NSString name -> 内存地址: 0x10481f0e0(常量区)
2020-11-02 11:19:39.128252+0800 001---- 内存验证[5104:87115] NSString name -> 指针地址: 0x7ffeeb3e0660(栈区)
2020-11-02 11:19:39.143804+0800 001---- 内存验证[5104:87115] NSObject objc -> 内存地址: 0x600002fc07d0(堆区)
2020-11-02 11:19:39.145034+0800 001---- 内存验证[5104:87115] NSObject objc -> 指针地址: 0x7ffeeb3e0658(栈区)