内存管理

一、内存分区

区域 管理方式 作用
保留区 系统使用 给系统提供一些必要的空间
代码区 系统自动管理 存储程序编译后的二进制代码
常量区 系统自动管理 存储字符串常量
静态全局区 系统自动管理 存储静态变量、全局变量
堆区 程序员管理 通过 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 后程序崩溃。

  • Tagged Pointer 专门用来存储小的对象:NSNumber、NSDate、NSString
  • Tagged Pointer 指针的值不再是地址,而是真正的值。是一个披着对象皮的普通变量。
  • 它的内存并不存储在堆中,也不需要 malloc 和 free,所以拥有极快的读取和创建速度。

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

详细文章

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271