iOS 底层原理

内存管理

2019-12-02  本文已影响0人  和风细羽

一、内存分区

区域 管理方式 作用
保留区 系统使用 给系统提供一些必要的空间
代码区 系统自动管理 存储程序编译后的二进制代码
常量区 系统自动管理 存储字符串常量
静态全局区 系统自动管理 存储静态变量、全局变量
堆区 程序员管理 通过 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 *));
}
  1. 不同数据类型在不同操作系统的内存占用

    数据类型 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 字节
  2. 对象指针

指针就是某块数据的地址

一个指针占用多少内存空间,与开发语言、它指向的那块数据及那块数据的数据类型都无关,仅仅与操作系统的寻址能力有关。

操作系统是多少位,一个指针就占用多少内存空间。在 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。

image

这就是内存对齐的处理。

详细:iOS 内存字节对齐

3.2 变量和内存的关系

变量只是某块内存在代码层的唯一指代,用来访问那块内存。变量本身并不存储于内存中,变量 ≠ 内存。

内存里存储的是具体的值。

我们每定义一个指定类型的变量,系统就会开辟一定大小的内存,并把这块内存的地址返回给变量指代,在代码层通过变量来操纵这块内存,再把等号右边的值存进这块内存。只不过指针变量除了可以操纵它自己对应的那块内存,还可以操纵它指向的那块内存。

  1. 数值型局部变量

    - (void)demo
    {
        int a = 11;
        NSLog(@"%p", &a);   // 获取变量 a 对应的地址值
        NSLog(@"%@", a);    // 获取地址内存储的内容
    }
    
    0x7ffee047019c
    11
    

    定义一个 int 类型的局部变量 a,系统在栈区开辟 4 个字节的内存,并把局部变量 a 作为这块内存在代码层的唯一指代,再把等号右边 11 这个值存进这块内存。

  1. 指针类型局部变量

    - (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 方法返回来的地址值存进这块内存。

  1. 字符串类型局部变量

    - (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 后程序崩溃。

以上只是简单的、杂乱的记录些知识点,详细的内容请看下面的链接。

详细文章

意一ineyee - 内存管理:部分基础知识
意一ineyee - 内存管理:不看白不看,看了就是赚
意一ineyee - 内存管理:实际开发需注意

上一篇 下一篇

猜你喜欢

热点阅读