iOS-内存管理(一)-布局&方案

内存布局

  • 栈区:由编译器分配和释放,是一种从高地址向低地址扩展的数据结构,是不连续的内存区域,先进后出(FILO)。主要用于存储函数、方法、指针、临时变量,简单类型的变量。优势是效率高。
  • 堆区:在运行时由程序员分配和释放,若程序员不释放,程序结束时可能由系统回收。堆是一种从低地址向高地址扩展的数据结构,是不连续的内存区域,以链表的方式进行存储。经过alloc生成的对象,或者block拷贝的对象等。相比栈区效率较低。
  • .bbs区:静态区域,系统分配和释放,用来存储未初始化的全局变量、静态变量。
  • .data区:系统分配和释放,用来存储初始化的全局变量、静态变量。
  • .text区:系统分配和释放,程序加载到内存中存储的区域。
- (void)test {
    // 存放放在栈区
    int a = 10
    
    // &obj存放在栈区
    // obj存放在堆区
    NSObject *obj = [NSObject alloc];
}
复制代码

栈效率比堆高的缘由以下:c++

  1. 有寄存器直接对栈进行访问,而对堆访问,只能是间接寻址。
  2. 栈中数据cpu命中率更高,知足局部性原理。
  3. 栈是编译时系统自动分配空间,而堆是动态分配(运行时分配空间),因此栈的速度快。
  4. 栈是先进后出的队列结构,比堆结构相对简单,分配速度大于堆。

局部变量

在函数内部声明,做用域只在当前{}以内。 存储位置:自动保存在函数的每次执行的【栈帧】中,并随着函数结束后自动释放。程序员

全局变量

全局变量是一种在函数外声明、能够跨文件访问的变量。它能够在声明时赋值,也能够在使用的时候赋值,若是在声明是没有赋值,系统会默认分配为空值。声明方式以下:bash

NSString *global;
NSString *global = @"AAA";
复制代码

全局变量保存在内存的全局存储区中,占用静态的存储单元;局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。数据结构

咱们能够在block内部直接访问全局变量,而不须要借助其余修饰词。多线程

使用全局变量不能在.h中定义,别的文件导入当前文件的时候会报错,duplicate symbols for architecture x86_64。由于在编译阶段,头文件的信息都会copy到文件中,这样的话会出现全部引入的头文件的都会有这么一个全局变量,因此会出现重名的状况。解决这种问题,咱们能够将其定义在.m文件中,而后在须要使用的地方使用extern关键字获取。架构

static

  • static关键字修饰局部变量时,只会初始化一次,在内存中只有一块地址。
- (void)staticLocalTest {
    for (int i = 0; i < 5; i++) {
        static int num = 0;
        num += 1;
        NSLog(@"==static num==%d==", num);
    }
}
复制代码

能够看出,被static修饰的变量,延长了生命周期,本该在for循环中每一次循环就销毁,实际上只会初始化一次,在for循环结束以后才销毁。async

  • static关键字修饰全局变量时,若是是在.m文件中,做用域仅限于当前文件,外部类是不能够访问到该全局变量的;若是是在.h文件中,能够被任意引入当前文件做为头文件的文件所使用。

静态全局变量

static 类型 变量名;
复制代码

静态全局变量在每一个文件中都会单独拷贝一份地址,互不影响。好比我在文件A中定义了一份静态全局变量,分别在ABC三个文件中使用,他们的赋值是互不影响的。而全局变量是只有一份地址,这是它们的不一样点。ide

内存管理方案

iOS系统根据使用场景的不一样,提供了3种方案:函数

  • TaggedPointer:对于一些小对象,如NSNumberNSDate等采用此种方案
  • NONPOINTER_ISA:64位架构下iOS应用程序
  • 散列表:散列表为复杂的数据结构,包含了引用计数表和弱引用表

1. TaggedPointer

TaggedPointer是苹果从64bit开始使用的一项内存管理技术,用于优化NSNumberNSDateNSString等小对象的存储。在没有使用TaggedPointer以前,这些小对象须要动态分配内存、维护引用计数等。使用了Tagged Pointer,指针的值再也不是地址了,而是真正的值。实际上它再也不是一个对象了,而是一个普通变量而已。因此,它的内存并不存储在堆中,不须要malloc申请、或者是释放。当指针不够存储数据时,才会使用动态分配内存的方式来存储数据。布局

如:没有使用TaggedPointer的时候,NSNumber指针存储的是堆中NSNumber对象的地址值。使用TaggedPointer以后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中。

经过下面例子咱们来看一下系统对TaggedPointer的特殊处理:

- (void)taggedpointerTest {
    self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"abc"];
             NSLog(@"%@",self.nameStr);
        });
    }
    
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"carshcarshcarshcarshcarshcarshcarshcarshcarshcarsh"];
            NSLog(@"%@",self.nameStr);
        });
    }
}
复制代码

运行程序,第二个for循环会崩溃,提示objc_release异常。崩溃的缘由是能够看到是由于多线程访问setter/getter,那么为何第一个for循环没有崩溃呢?从代码看两个for循环的区别就是第一个nameStr赋值短,第二个长一些。打断点,在控制台查看信息,能够看到第一个循环中的nameStrNSTaggedPointerString *,而第二个是NSCFString *。查看retain/release的源码,能够看到:

id objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

id objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}
复制代码

刚才咱们说了,TaggedPointer的存储是类型+数据,那么咱们来具体看一下,它的存储策略:

  • 指针地址首位肯定类型,用来区分NSNumberNSDateNSString。如,0xb表明NSNumber
  • 若是是字符串,最后一位存储字符串长度
  • 其他位利用ASCII码编码值来存储字符

2. NONPOINTER_ISA

NONPOINTER_ISAMACOSX_VERSION_10_11以后出来的对isa内存的优化。在isa中添加了更多的信息。

uintptr_t nonpointer        : 1;                                    
uintptr_t has_assoc         : 1;                                    
uintptr_t has_cxx_dtor      : 1;                                    
uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic             : 6;                                    
uintptr_t weakly_referenced : 1;                                    
uintptr_t deallocating      : 1;                                    
uintptr_t has_sidetable_rc  : 1;                                    
uintptr_t extra_rc          : 19
复制代码
  • nonpointer:表示是否对isa指针开启指针优化。0表示纯isa指针,11表示不止是类对象地址,isa中包含了类信息、对象的引用计数等。
  • has_assoc:关联对象标志位,0没有,1存在。
  • has_cxx_dtor:该对象是否有c++或者objc的析构器,若是有析构函数,则须要作析构逻辑,若是没有,则能够更快的释放对象。
  • shiftcls:存储类指针的值。开启指针优化的状况下,在arm64架构中有33位用来存储类指针。
  • magic:用于调试器判断当前对象是真的对象仍是没有初始化的空间。
  • weakly_referenced:志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象能够更快释放。
  • deallocating:标志对象是否正在释放内存。
  • has_sidetable_rc:当对象引用技术大于10时,则须要借用该变量存储进位。
  • extra_rc:当表示该对象的引用计数值,其实是引用计数值减1, 例如,若是对象的引用计数为10,那么extra_rc为9。若是引用计数大于10,则须要使用到has_sidetable_rc

3. 散列表

散列表是一张哈希结构的表,其包含了自旋锁、引用计数表、弱引用表。其中的引用计数表,就是来对内存管理作处理的。

相关文章
相关标签/搜索