前言
内存管理是开发过程中不可忽视的部分,出现的很多问题都和内存有关。我们都知道内存的五大区,那么它是怎样布局的,接下来本将对它进行讲解。
内存分区与布局
- 以
4G
手机为例的内存五大区与布局
如图所示:
栈区(Stack)
- 栈区是一块
连续的
内存空间,它的结构是从高地址往低地址
拉伸,遵循先进后出(FILO)
原则 - 栈区存储的是
局部变量
,函数
,方法
,参数
,指针
- 栈区的地址空间一般是以
0x7
开头 - 栈区由
编译器
自动分配内存和释放
举例:
- (void)testStack{
// 栈区
int a = 10;
int b = 20;
NSObject *object = [NSObject new];
NSLog(@"a == %p",&a);
NSLog(@"b == %p",&b);
NSLog(@"object == %p",&object);
NSLog(@"%lu",sizeof(&object));
NSLog(@"%lu",sizeof(a));
}
打印结果如下:
- 可以观察出栈区内存连续,且内存是从高地址往低地址扩展,内存是
0x7
开头
堆区(Heap)
- 堆区是一块
不连续
的内存空间,它的结构是从低地址向高地址
扩展 - 堆区存储的是
对象
,需要开辟空间
的东西,栈区内存比较小,堆区比较大,所以在堆区开辟空间 - 堆区地址空间一般以
0x6
开头
举例:
- (void)testHeap{
// 堆区
NSObject *object1 = [NSObject new];
NSObject *object2 = [NSObject new];
NSObject *object3 = [NSObject new];
NSObject *object4 = [NSObject new];
NSObject *object5 = [NSObject new];
NSObject *object6 = [NSObject new];
NSObject *object7 = [NSObject new];
NSObject *object8 = [NSObject new];
NSObject *object9 = [NSObject new];
NSLog(@"object1 = %@",object1);
NSLog(@"object2 = %@",object2);
NSLog(@"object3 = %@",object3);
NSLog(@"object4 = %@",object4);
NSLog(@"object5 = %@",object5);
NSLog(@"object6 = %@",object6);
NSLog(@"object7 = %@",object7);
NSLog(@"object8 = %@",object8);
NSLog(@"object9 = %@",object9);
}
结果如下:
- 从打印结果中可以看出来堆区的内存是不连续的,且是
0x6开头
全局区(静态区)
- 全局区又分为
.bss段
和.data段
,内存地址一般由0x1
开头:Bss
段:未初始化
的全局变量,静态变量,Data
段:已初始化
的全局变量,静态变量
- 举例:
- (void)globalTest { // 全局区 NSLog(@"************ bss ************"); NSLog(@"bssA == \t%p",&bssA); NSLog(@"bssB == \t%p",&bssB); NSLog(@"bssStr == \t%p",&bssStr); NSLog(@"************ data ************"); NSLog(@"dataA == \t%p",&dataA); NSLog(@"dataB == \t%p",&dataB); NSLog(@"dataStr == \t%p",&dataStr); }
-
运行结果如下:
-
静态安全测试
-
经常有人说静态区也称作
静态安全区
,下面我们就来验证它为什么安全,创建一个WSPerson
类,以及它的分类:// WSPerson.h static int ws_number = 100; @interface WSPerson : NSObject - (void)eat; + (void)sleep; @end // WSPerson.m - (void)eat { ws_number++; NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number); } + (void)sleep { ws_number++; NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number); } // WSPerson+App.h @interface WSPerson (App) - (void)cate_test; @end // WSPerson+App.m - (void)cate_test { ws_number++; NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number); }
在
ViewController
中的调用代码如下:- (void)constTest { [[WSPerson alloc] eat]; NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number); ws_number = 10000; NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number); [[WSPerson alloc] eat]; NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number); [WSPerson sleep]; NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number); [[WSPerson alloc] cate_test]; NSLog(@"%s __ %p __ %d", __func__, &ws_number, ws_number); }
打印出的结果比较有意思:
三个文件
ws_number
的内存地址不一样,具体是原因需要借助Clang
查看三个文件的C++
代码:从源码中可以观察到每个文件都有个
ws_number
的全局变量,且初始值都一样。也就是说在多个文件使用同一个静态变量,系统会各自生成一个相同初始值
且地址不同
的静态变量,这样在各自文件内使用就不会互相干扰,数据比较安全。
常量区(.rodata)
- 常量区的内存在
编译时
就已经确定,主要存放已经使用过的,且没有指向的字符串常量
(因为字符串常量可能在程序中多次被使用,所以在程序运行之前
就会提前分配内存)。常量区的常量在程序结束后,由系统释放
代码区(.text)
- 存储程序代码,在编译时加载到内存中,代码会被编译成
二进制的形式
进行存储
其他
-
- 我们可以从图中看出,栈底的内存为
0c0000000
,转成10进制
后等于3GB
,还有1G
的内存分配给了内核区
。
内核区
:主要进行消息处理
以及多线程的操作
- 我们可以从图中看出,栈底的内存为
-
- 代码区的地址是从
0x00400000
开始,怎么不是从0x0
开始? 因为0x0
是nil
,从nil
开始分配会出现问题,所以在代码区之前就有了保留区
保留区
:预留给系统处理nil
等
- 代码区的地址是从
相关面试题
-
- 为什么说
Static
修饰的变量不占内存?
- 答:因为
Static
修饰的变量修饰的变量在全局区,它不占用自己分配的内存
- 为什么说
-
- 栈区的内存根据经验如何定位?
- 答:栈区的内存是通过sp寄存器定位的,堆区是通过寄存器定位到这一个包含内存的地址,通过地址定位内存的位置,所以决定栈区和堆区的速度,
栈区的速度
是非常快的