内存管理系列文章:
前言
OC底层实现其实都是C语言的代码,所以想深入理解iOS的内存管理机制,可以通过了解C语言的内存管理来进一步熟悉OC的内存管理。
内存分类
- RAM:运行内存,不能掉电储存;
- ROM:储存性内存,可以掉电储存,例如:内存卡,flash;
RAM和ROM的区别
1、 RAM的访问速度要远高于ROM,价格也要高;
2、 CPU只能从RAM直接读取指令;
3、 app程序一般存放于ROM中。启动app时,系统会把开启的app程序从ROM中转移到RAM中
从内存分配看app加载过程
在了解OC的内存分配之前,先看下app的加载过程:
1、 当APP没有打开时,ipa或者app文件都是存在于ROM中,即我们说一个叫Application的文件夹中。
2、 在APP启动的时候iOS系统会先为app从RAM中分配一个独立的内存空间(即沙盒),app所有的内存操作都在这个独立的沙盒中进行。
3、 接着系统会先加载二进制代码到内存中,然后加载常量区中的常量,接着加载全局区和静态区(初始化过的静态区和没有初始化过的静态区是分开的)
4、 之后程序会找main入口函数开始执行代码,在执行代码的过程中,会创建对象和一些局部变量,其中对象存放在堆中,变量存放在栈上。
内存分区
画图工具没找到太合适的,粗略的做了一张图,通过以下这张图对内存布局有个大概认识:
区段解释
1、 程序代码区:代码区用来存放函数体的二进制代码,程序结束后由系统释放
2、 常量区: 常量区用来存放常量字符串等,程序结束后由系统释放
3、 静态区:全局变量和静态变量的存储是放在一块的,程序结束后由系统释放
- 初始化的全局变量和静态变量在一块区域,.data。
- 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,.bSS。
全局变量和静态变量要尽量少用。因为这些变量在程序的生命周期中不会变释放,比较容易占用内存空间,不适合存储比较大量的数据
1、 堆区(heap):
- 堆区的地址是从低到高分配,所以先声明的变量地址要比后声明的变量地址小。
- 一般由程序员分(new、malloc)释放,若程序员不释放,程序结束时可能由操作系统回收
- 堆是向高地址扩展的数据结构,,是一块不连续的内存的区域。引文系统是用链表来存储的空闲内存地址的。
- 在ios中,堆区的内存是应用程序共享的,堆中的内存分配是系统负责的
1、 栈区(stack):
- 存放函数的参数值、局部变量的值等,由编译器自动分配释放,通常在函数执行结束后就释放了,iOS中栈区的大小是2M。
- 栈上的地址是从高到低分配,先声明的变量地址比后声明的变量地址要大。
- 栈是向低地址扩展的数据结构,是一块连续的内存的区域
堆空间的申请
1、 操作系统有一个记录空闲内存地址的链表。
2、 当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
3、 由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中
栈空间的申请
每一个函数在执行的时候都会向操作系统索要资源,栈区就是函数运行时的内存,栈区中的变量由编译器负责分配和释放,内存随着函数的运行分配,随着函数的结束而释放,由系统自动完成。
- 静态分配是编译器完成的,比如自动变量(auto)的分配。
- 动态分配由alloca函数完成。栈的动态分配无需释放(是自动的),也就没有释放函数
堆栈的区别
- 按管理方式分
1、 对于栈来讲,是由系统编译器自动管理,不需要程序员手动管理
2、 对于堆来讲,释放工作由程序员手动管理,不及时回收容易产生内存泄露
- 按分配方式分
1、 堆是动态分配和回收内存的,没有静态分配的堆
2、 栈有两种分配方式:静态分配和动态分配
* 静态分配是系统编译器完成的,比如局部变量的分配
* 动态分配是有alloc函数进行分配的,但是栈的动态分配和堆是不同的,它的动态分配也由系统编译器进行释放,不需要程序员手动管理
小结
个人觉得主要弄清楚以下几点
1、 内存分配的地址从低到高,还是从高到低
2、 每个段或者区存放的变量是什么类型的
3、 栈和堆的管理、申请、释放、以及两者区别等,栈和堆是一个重点
demo示例【很重要】
运行环境:选择的模拟器是Iphone11 Pro Max
static int a = 10;
static int b;
- (void)viewDidLoad {
[super viewDidLoad];
/* 代码段 */
IMP imp = method_getImplementation(class_getInstanceMethod(self.class, @selector(viewDidLoad)));
NSLog(@"【代码段】==> 编译之后的函数");
NSLog(@"imp --- %p", imp);
/* 数据段 */
static int c = 10;
static int d;
/* 常量区 */
NSString *str1 = @"脚底按摩"; // 直接写出来的,不是通过方法创建的字符串,编译时会生成为【字符串常量】
NSString *str2 = @"精油推背";
NSLog(@"【数据段/常量区】==> 字符串常量");
NSLog(@"str1 --- %p", str1);
NSLog(@"str2 --- %p", str2);
/* 静态初始化区 */
NSLog(@"【数据段/静态区】==> 已初始化数据");
NSLog(@"c ------ %p", &c);
NSLog(@"a ------ %p", &a);
/* 静态未初始化区 */
NSLog(@"【数据段/静态区】==> 未初始化数据");
NSLog(@"d ------ %p", &d);
NSLog(@"b ------ %p", &b);
/* 堆 */
NSObject *obj = [[NSObject alloc] init];
NSString *str3 = [NSString stringWithFormat:@"%@", @"测试字符串是否在堆上"];
NSLog(@"【堆】==> 实例对象"); // 分配的内存空间地址【越来越大】,不连续的
NSLog(@"obj ---- %p", obj);
NSLog(@"str3 --- %p", str3);
/* 栈 */
int e = 20;
int f;
NSLog(@"【栈】==> 局部变量"); // 分配的内存空间地址【越来越小】,是连续的,不管有没有初始化都会分配
NSLog(@"e ------ %p", &e);
NSLog(@"f ------ %p", &f);
}
// 打印结果
2020-03-24 23:05:05.707713+0800 03-内存管理-内存布局[3812:249391] 【代码段】==> 编译之后的函数
2020-03-24 23:05:05.707854+0800 03-内存管理-内存布局[3812:249391] imp --- 0x10f55cae0
2020-03-24 23:05:05.707985+0800 03-内存管理-内存布局[3812:249391] 【数据段/常量区】==> 字符串常量
2020-03-24 23:05:05.708091+0800 03-内存管理-内存布局[3812:249391] str1 --- 0x10f55f060
2020-03-24 23:05:05.708197+0800 03-内存管理-内存布局[3812:249391] str2 --- 0x10f55f080
2020-03-24 23:05:05.708307+0800 03-内存管理-内存布局[3812:249391] 【数据段/静态区】==> 已初始化数据
2020-03-24 23:05:05.708411+0800 03-内存管理-内存布局[3812:249391] c ------ 0x10f5614c0
2020-03-24 23:05:05.708510+0800 03-内存管理-内存布局[3812:249391] a ------ 0x10f5614c4
2020-03-24 23:05:05.708621+0800 03-内存管理-内存布局[3812:249391] 【数据段/静态区】==> 未初始化数据
2020-03-24 23:05:05.708723+0800 03-内存管理-内存布局[3812:249391] d ------ 0x10f561648
2020-03-24 23:05:05.708982+0800 03-内存管理-内存布局[3812:249391] b ------ 0x10f56164c
2020-03-24 23:05:05.719411+0800 03-内存管理-内存布局[3812:249391] 【堆】==> 实例对象
2020-03-24 23:05:05.719575+0800 03-内存管理-内存布局[3812:249391] obj ---- 0x600002a05610
2020-03-24 23:05:05.719692+0800 03-内存管理-内存布局[3812:249391] str3 --- 0x600002642400
2020-03-24 23:05:05.719810+0800 03-内存管理-内存布局[3812:249391] 【栈】==> 局部变量
2020-03-24 23:05:05.719932+0800 03-内存管理-内存布局[3812:249391] e ------ 0x7ffee06a10c4
2020-03-24 23:05:05.720063+0800 03-内存管理-内存布局[3812:249391] f ------ 0x7ffee06a10c0
总结
1、 栈的地址一般是0x7开始,堆是0x6开始,而数据段是0x1
2、 静态区是在同一段内存连续分配的,按内存地址增长方向分配。
3、 未初始化静态区的地址比初始化的静态区地址更大,
4、 栈空间也是同一段内存连续分配的,按内存地址减小方向分配
问题和探讨
- ❓❓❓堆区的地址是从低到高分配,先声明的变量地址要比后声明的变量地址小,但是demo的输出结果,却是从高到低分配的,这个还有待进一步了解
NSObject *obj = [[NSObject alloc] init];
NSString *str3 = [NSString stringWithFormat:@"%@", @"测试字符串是否在堆上"];
2020-03-24 23:05:05.719411+0800 03-内存管理-内存布局[3812:249391] 【堆】==> 实例对象
2020-03-24 23:05:05.719575+0800 03-内存管理-内存布局[3812:249391] obj ---- 0x600002a05610
2020-03-24 23:05:05.719692+0800 03-内存管理-内存布局[3812:249391] str3 --- 0x600002642400