iOS程序的内存布局

image.png

  • 栈区(stack):
    • 存放函数的参数值、局部变量的值等,
    • 由编译器自动分配释放,通常在函数执行结束后就释放了,其操作方式类似数据结构中的栈。
    • 栈内存分配运算内置于处理器的指令集,效率很高。
    • 分配的内存容量有限,比如iOS中栈区的大小是512k。
  • 堆区(heap)
    • C 语言使用 malloc、calloc、realloc 函数分配的空间,需要使用 free 函数释放
    • 由程序员分配释放,若程序员不释放,会出现内存泄漏
    • 分配方式类似于链表。
    • 堆区的大小由系统决定,包括:系统内存/磁盘交换空间

栈区的特点

  • 栈区的地址是连续的
  • 栈区地址按照分配的顺序,由大到小顺序排列
  • 访问速度快、栈区的内存由系统管理、后进先出/先进后出

堆区的特点

堆区的大小由系统决定,包括:系统内存/磁盘交换空间

  • 所有程序共享、存储大数据、程序员管理
  • 堆区的地址是不连续的
  • 速度没有栈区快

    • 堆区的访问速度没有栈区快,因为我们要访问堆区中创建对象的属性, 必须先需要通过变量找到栈区的地址,再通过地址定位到堆区中的某一个位置, 只有找个这个位置之后,我们才可以访问到存储到这个对象中属性对应的数值.由于有了 这个地址寻找的过程,所有速度没有栈区的快.

      静态变量

  • 如果只有一个方法使用,将 静态变量 定义在方法内部

  • 如果有多个方法使用,将 静态变量 定义在 .m 中
  • 不要把静态变量定义在头文件中

    常量

  • 数据段 为常量分配空间

  • const 关键字保证其后修饰的常量的值不允许被修改
  • 在程序被加载到内存时,就会为常量分配空间并且设置初始值
  • 如果没有指定初始值,会使用 0 作为初始值
  • 常量名应该尽量的长以避免出现重名
    1. //在 .m 中定义常量并且设置初始值
    2. const NSInteger cNum = 99;
    3. //在 .h 中使用 extern 关键字声明常量在其他位置定义并且已经赋值,外部可以直接使用
    4. extern const NSInteger cNum;

    验证

  1. int a = 10;
  2. int b;
  3. int main(int argc, char * argv[]) {
  4. @autoreleasepool {
  5. static int c = 20;
  6. static int d;
  7. int e;
  8. int f = 20;
  9. NSString *str = @"123";
  10. NSObject *obj = [[NSObject alloc] init];
  11. NSLog(@"\n&a=%p\n&b=%p\n&c=%p\n&d=%p\n&e=%p\n&f=%p\nstr=%p\nobj=%p\n",
  12. &a, &b, &c, &d, &e, &f, str, obj);
  13. return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  14. }
  15. }
  16. /*
  17. 字符串常量
  18. str=0x10dfa0068
  19. 已初始化的全局变量、静态变量
  20. &a =0x10dfa0db8
  21. &c =0x10dfa0dbc
  22. 未初始化的全局变量、静态变量
  23. &d =0x10dfa0e80
  24. &b =0x10dfa0e84
  25. obj=0x608000012210
  26. &f =0x7ffee1c60fe0
  27. &e =0x7ffee1c60fe4
  28. */

Tagged Pointer

  • 64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumberNSDateNSString等小对象的存储
  • 在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值

image.png

  • 使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
  • 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
  • objc_msgSend能识别Tagged Pointer,比如NSNumberintValue方法,直接从指针提取数据,节省了以前的调用开销

    判断是否为Tagged Pointer

    image.png
    如何判断一个指针是否为Tagged Pointer?

  • iOS平台,最高有效位是1(第64bit)

  • Mac平台,最低有效位是1

OC对象的内存管理

  • 在iOS中,使用引用计数来管理OC对象的内存
  • 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
  • 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
  • 内存管理的经验总结
    • 当调用allocnewcopymutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
    • 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
  • 可以通过以下私有函数来查看自动释放池的情况extern void _objc_autoreleasePoolPrint(void);

copy和mutableCopy

  • 可变对象的 copymutableCopy 都是深拷贝
  • 不可变对象的 copy 是浅拷贝,mutableCopy 是深拷贝
  • copy 方法返回的都是不可变对象 | | copy | mutableCopy | | —- | —- | —- | | NSString | NSString

浅拷贝 | NSMutableString

深拷贝 | | NSMutableString | NSString

深拷贝 | NSMutableString

深拷贝 | | NSArray | NSArray

浅拷贝 | NSMutableArray

深拷贝 | | NSMutableArray | NSArray

深拷贝 | NSMutableArray

深拷贝 | | NSDictionary | NSDictionary

浅拷贝 | NSMutableDictionary

深拷贝 | | NSMutableDictionary | NSDictionary

深拷贝 | NSMutableDictionary

深拷贝 |

引用计数的存储

64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中
image.png

  • refcnts是一个存放着对象引用计数的散列表

    面试题

    1. 以下2段代码能发生什么事?有什么区别?

    image.png

  • 第一段会发生崩溃,多线程会重复对_name 做release释放操作导致崩溃

  • 第二段不会崩溃,因为NSString 数据较少时会使用Tagged Pointer,会直接从指针提取数据做赋值操作