iOS内存五大区
在iOS中,内存主要分为:栈区、堆区、全局区(静态区)、常量区以及代码区这五大区。本文将对这五大区进行相关分析。
以4GB内存举例,其内存结构如下
1. 栈区
1.1 栈区简介
- 栈是系统级数据结构,对应唯一的线程(iOS是单进程)
- 栈是从高地址向低地址扩展的,遵循
FILO
先进后出原则 - 栈是一块连续的内存区域,一般以
0x7
开头 - 栈遵循先进后出原则
- 栈内存是在运行时分配的
1.2 栈的存储
- 栈内存的创建和释放是由编译器自动管理的
- 栈区主要存储局部变量(函数/方法内部创建的变量),离开作用域就会销毁释放
- 栈还可以存储函数/方法的参数,包括隐藏参数(
id self,SEL _cmd
)和返回值
1.3 栈的优缺点
-
优点:
- 连续区域,存储和查找速度快
- 编译器自动创建和销毁,安全可靠,不会产生内存碎片
-
缺点
- 因为连续,所以大小有限制
- 在iOS中主线程一般是
1M
,其他线程是512kb
- 我们也可以通过线程的
stackSpace
去修改其大小,但是成本有些大
2. 堆区
2.1 堆区简介
- 堆内存是由低地址向高地址扩展的(与栈相反)
- 堆的内存区域是不连续的,方便增、删、改
- 堆的地址空间是以
0x6
开头的 - 堆遵循先进先出FIFO原则
- 堆一般是在运行时分配内存
2.2 堆的存储
- 在iOS中堆一般存储
new
、alloc
、malloc
、realloc
创建的内容 - 目前iOS都是
ARC
内存管理,一般不需要手动释放堆区内存
2.3 堆的优缺点
优点:
- 由于内存不连续,不用查找位置进行存储
缺点:
- 由于内存不连续,容易产生内存碎片
- 需要注意野指针,内存泄漏等问题
3. 全局区(静态区.bss)
全局区(静态区)是编译时分配的内存区域。在iOS中一般以0x1
开头,在程序运行时一直存在,直到程序运行结束才会被释放
全局区主要存储全局变量和静态变量,对于已经初始化的和未初始化的会进行分开存储
一般static
修饰的变量为静态变量会存储在全局区,在编译时创建
static
即可以修饰局部变量也可以修饰全局变量
4. 常量区(.data)
常量区是一块比较特殊的存储区,常量区里面存放的是常量,比如整形、字符型、浮点型、常量字符串,常量区的内存是在编译阶段进行分配,程序运行时会一直存储在内存中,也是以0x1
开头,只有当程序结束后才会由操作系统释放。
5. 代码区
代码区就是存储可执行代码的区域,在编译器进行分配内存,这块区域是只读的,也就增加了安全性,使我们的程序在运行时不能修改本来的代码。
6. 内核区&保留区
内核区就是系统内核运行的区域,保留区就是保留的一块区域,留个系统处理nil
等情况。
7. 打印内存地址
- (void)testMemory {
int a = 123;
NSLog(@"内存地址: %p", &a); // 【局部变量】 栈区
NSObject * objc = [NSObject new];
NSLog(@"内存地址: %p", objc);// 【对象的内容】 存放在堆区
NSLog(@"指针地址: %p", &objc);//【对象的指针】 存放在栈区
NSString * name = @"memory";
NSLog(@"内存地址: %p", name); // 【字符串内容】 存放在常量区
NSLog(@"指针地址: %p", &name);// 【局部变量name的指针】 存放在栈区
}
8. OC中全局变量的坑点
首先先看一下代码:
Person 类代码:
#import
static int staticNumber = 100;
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
- (void)run;
+ (void)eat;
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@implementation Person
- (void)run{
staticNumber ++;
NSLog(@"Person run:%@-%p--%d",self,&staticNumber,staticNumber);
}
+ (void)eat{
staticNumber ++;
NSLog(@"Person eat:%@-%p--%d",self,&staticNumber,staticNumber);
}
@end
Person 分类代码:
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person (Cate)
- (void)cate_method;
@end
NS_ASSUME_NONNULL_END
#import "Person+Cate.h"
@implementation Person (Cate)
- (void)cate_method{
NSLog(@"Person cate:%@-%p--%d",self,&staticNumber,staticNumber);
}
@end
那么下面这段代码的打印结果是什么呢?
- (void)testStaticNumber {
NSLog(@"************全局静态变量坑点************");
// 100 可以修改
// 只针对文件有效 -
NSLog(@"vc:%p--%d",&staticNumber,staticNumber); // 100
staticNumber = 10000;
NSLog(@"vc:%p--%d",&staticNumber,staticNumber); // 10000
[[Person new] run]; // 100 + 1 = 101
NSLog(@"vc:%p--%d",&staticNumber,staticNumber); // 10000
[Person eat]; // 101 + 1 = 102
NSLog(@"vc:%p--%d",&staticNumber,staticNumber); // 10000
[[Person alloc] cate_method]; // 100
}
打印结果如下:
通过该打印结果能说明,在Objective-C
中的全局静态变量只针对文件有效!!!
总结
栈区、堆区内存空间是运行时分配的,因此随着程序运行而变化;在iOS中堆区的内存是应用程序共享的,堆区的内存分配是系统负责的。全局区(静态区)、常量区、代码区是在编译时分配,是固定的不可变的。