一、内存分区
区域 | 管理方式 | 作用 |
---|---|---|
保留区 | 系统使用 | 给系统提供一些必要的空间 |
代码区 | 系统自动管理 | 存储程序编译后的二进制代码 |
常量区 | 系统自动管理 | 存储字符串常量 |
静态全局区 | 系统自动管理 | 存储静态变量、全局变量 |
堆区 | 程序员管理 | 通过 alloc 创建的对象都存储在这里,地址由低到高分配 |
栈区 | 系统自动管理 | 存储局部变量,地址由高到低分配 |
内核区 | 系统使用 |
栈区从上往下走,堆区会从下往上走,当两者相遇的时候,则会发生堆栈溢出。
int a = 11; // 已初始化的全局变量 -> 0x1 -> 静态全局区
int b; // 未初始化的全局变量 -> 0x1 -> 静态全局区
static int c = 12; // 已初始化的静态全局变量 -> 0x1 -> 静态全局区
static int d; // 未初始化的静态全局变量 -> 0x1 -> 静态全局区
- (void)memory
{
static int e = 13; // 已初始化的静态局部变量 -> 0x1 -> 静态全局区
static int f; // 未初始化的静态局部变量 -> 0x1 -> 静态全局区
NSLog(@"a = %p, c = %p, e = %p", &a, &c, &e);
NSLog(@"b = %p, d = %p, f = %p", &b, &d, &f);
int g = 14; // 已初始化的局部变量 -> 0x7 -> 栈区
int h; // 未初始化的局部变量 -> 0x7 -> 栈区
NSLog(@"g = %p, h = %p", &g, &h);
NSString * s1 = @"a"; // 局部变量 s1 -> 0x7 -> 栈区,s1 指向的对象 -> 0x1 -> 常量区
NSString * s2 = @"b"; // 局部变量 s2 -> 0x7 -> 栈区,s2 指向的对象 -> 0x1 -> 常量区
NSLog(@"&s1 = %p, s1 = %p, &s2 = %p, s2 = %p", &s1, s1, &s2, s2);
NSObject * obj1 = [[NSObject alloc] init]; // 局部变量 obj1 -> 0x7 -> 栈区,obj1 指向的对象 -> 0x6 -> 堆区
NSObject * obj2 = [[NSObject alloc] init]; // 局部变量 obj2 -> 0x7 -> 栈区,obj2 指向的对象 -> 0x6 -> 堆区
NSLog(@"&obj1 = %p, obj1 = %p, &obj2 = %p, obj2 = %p", &obj1, obj1, &obj2, obj2);
}
a = 0x103403a88, c = 0x103403a90, e = 0x103403a8c
b = 0x103403cf0, d = 0x103403b6c, f = 0x103403b68
g = 0x7ffeec8034a0, h = 0x7ffeec8034a4
&s1 = 0x7ffeec8034a8, s1 = 0x1033fffc0, &s2 = 0x7ffeec8034b0, s2 = 0x1033fffe0
&obj1 = 0x7ffeec803490, obj1 = 0x600000dd4900, &obj2 = 0x7ffeec803498, obj2 = 0x600000dd47e0
二、占用内存大小
- (void)memory
{
// char
NSLog(@"char : %zd 字节", sizeof(char));
// unsigned char
NSLog(@"unsigned char : %zd 字节", sizeof(unsigned char));
// short
NSLog(@"short : %zd 字节", sizeof(short));
// unsigned short
NSLog(@"unsigned short : %zd 字节", sizeof(unsigned short));
// int
NSLog(@"int : %zd 字节", sizeof(int));
// unsigned int
NSLog(@"unsigned int : %zd 字节", sizeof(unsigned int));
// long
NSLog(@"long : %zd 字节", sizeof(long));
// unsigned long
NSLog(@"unsigned long : %zd 字节", sizeof(unsigned long));
// long long
NSLog(@"long long: %zd 字节", sizeof(long long));
// unsigned long long
NSLog(@"unsigned long long: %zd 字节", sizeof(unsigned long long));
// float
NSLog(@"float : %zd 字节", sizeof(float));
// double
NSLog(@"double : %zd 字节", sizeof(double));
// 对象
NSLog(@"NSObject : %zd 字节", sizeof(NSObject *));
}
-
不同数据类型在不同操作系统的内存占用
数据类型 32 位操作系统占用内存空间 64位操作系统占用内存空间 char 1 字节 1 字节 unsigned char 1 字节 1 字节 short 2 字节 2 字节 unsigned short 2 字节 2 字节 int 4 字节 4 字节 unsigned int 4 字节 4 字节 long 4 字节 8
字节unsigned long 4 字节 8
字节long long 8 字节 8 字节 unsigned long long 8 字节 8 字节 float 4 字节 4 字节 double 8 字节 8 字节 NSObject * 4 字节 8
字节 对象指针
指针就是某块数据的地址
一个指针占用多少内存空间,与开发语言、它指向的那块数据及那块数据的数据类型都无关,仅仅与操作系统的寻址能力
有关。
操作系统是多少位,一个指针就占用多少内存空间。在 32 位的系统上,一个指针占 4 个字节;64 位的系统占 8 个字节。
三、对象分配内存
OC 对象的本质就是结构体
3.1 内存对齐
内存对齐是指系统会按照一定的规则,为某块数据安排地址和实际占用的内存大小。
系统在给某块数据分配内存空间时,并不是在内存中一段接一段的连续开辟,而是有可能出现填充字节。
struct s {
short a;
int b;
};
- (void)memory
{
struct s s1;
s1.a = 1;
s1.b = 2;
NSLog(@"%zd, &p = %p", sizeof(s1), &s1);
}
8, &p = 0xbffe4a60
按照前面的知识,一个 short 类型占用 2 个字节,一个 int 类型占用 4 个字节,所以结构体 s 占用 2 + 4 = 6 个字节,结构体 s 在内存上的布局就是 2 个字节的 short + 4 个字节的 int。
但是实际上,结构体 s 占用了 8 个字节,它在内存空间上的布局实际上是 2 个字节的 short + 2 个字节的填充 + 4 个字节的 int。
这就是内存对齐的处理。
详细:iOS 内存字节对齐
3.2 变量和内存的关系
变量只是某块内存在代码层的唯一指代,用来访问那块内存。变量本身并不存储于内存中,变量 ≠ 内存。
内存里存储的是具体的值。
我们每定义一个指定类型的变量,系统就会开辟一定大小的内存,并把这块内存的地址返回给变量指代,在代码层通过变量来操纵这块内存,再把等号右边的值存进这块内存。只不过指针变量除了可以操纵它自己对应的那块内存,还可以操纵它指向的那块内存。
-
数值型局部变量
- (void)demo { int a = 11; NSLog(@"%p", &a); // 获取变量 a 对应的地址值 NSLog(@"%@", a); // 获取地址内存储的内容 } 0x7ffee047019c 11
定义一个 int 类型的局部变量 a,系统在栈区开辟 4 个字节的内存,并把局部变量 a 作为这块内存在代码层的唯一指代,再把等号右边 11 这个值存进这块内存。
-
指针类型局部变量
- (void)demo { NSObject * objc = [[NSObject alloc] init]; NSLog(@"%p", &obj); // 获取局部变量 obj 就是获取指代的内存地址(栈区) NSLog(@"%p", obj); // 获取局部变量 obj 的内容,也就是指代的内存地址内的内容,是 alloc 分配的堆区地址 NSLog(@"%@", obj); // 获取变量 obj 指代的内存地址(堆区地址)内的内容(栈区地址)的内容 } 0x7ffee178f198 0x60000234d0d0 <NSObject: 0x60000234d0d0>
定义指针类型的局部变量 obj,系统会在栈区开辟 8 个字节,在栈区开辟 N 个字节的内存,并把变量 objc 作为栈区这块内存在代码层的唯一指代,再把等号右边 init 方法返回来的地址值存进这块内存。
-
字符串类型局部变量
- (void)demo { NSString * s = @"a"; NSLog(@"&s = %p, s = %p", &s, s); } &s = 0x7ffee8aa14a8, s = 0x107162fa0
定义指针类型的局部变量 s,系统会在栈区开辟 8 个字节,并把变量 objc 作为栈区这块内存在代码层的唯一指代,因为字符串常量是存放在常量区的,再把常量区返回来的地址值存进这块栈区地址内。
四、Tagged Pointer
- (void)test1
{
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcde"];
});
}
}
- (void)test2
{
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghjsfdasdfas"];
});
}
}
static NSInteger i = 0;
- (void)setName:(NSString *)name
{
if (_name != name) {
id pre = _name;
// 1.先保留新值
[name retain];
// 2.再进行赋值
_name = name;
// 3.释放旧值
[pre release];
NSLog(@"%ld", (long)i++);
}
}
当执行 [self test1];
,输出如下:
0
当执行 [self test2];
,输出如下:
0
1
0
2
2
1
3
4
5
...
996
在打印 996 后程序崩溃。
- Tagged Pointer 专门用来存储小的对象:NSNumber、NSDate、NSString
- Tagged Pointer 指针的值不再是地址,而是真正的值。是一个披着对象皮的普通变量。
- 它的内存并不存储在堆中,也不需要 malloc 和 free,所以拥有极快的读取和创建速度。
以上只是简单的、杂乱的记录些知识点,详细的内容请看下面的链接。
详细文章
意一ineyee - 内存管理:部分基础知识
意一ineyee - 内存管理:不看白不看,看了就是赚
意一ineyee - 内存管理:实际开发需注意