这是我参与「第四届青训营 」笔记创作活动的第4天
介绍
Heap & Stack 栈区,堆区
Stack(栈)
存储push & 销毁pop
stack pointer 栈点位置(高地址向低地址扩展)
栈内存操作例子:
寄存器里有1234的内容,需要放到栈里。
- 将1234push进去,栈点位置会递减(stack pointer decremented),因为栈点位置从高地址向低地址扩展,所以栈点位置--,指向新的栈点(1234存储位置)
- 操作之后,不需要1234,将它pop出去,销毁之后栈会缩小,栈点递增(stack pointer incremented),回到之前的栈点位置
Heap(堆)
heap 与 heap数据结构 无关
存储数组和对象,线程共享
手动分配内容
由低地址向高地址拓展
heap内存例子:
int a;在stack里创建一个临时变量a
int *p在创建一个临时的整数类型、指针变量p
p = (int*)malloc(sizeof(int));通过malloc在heap里创建一个四子节的整数空间,让p指向heap里的这个内存【语句给指针变量分配一个整型存储空间】
*p = 10;通过*p的操作在heap里400的位置存入10
free(p);函数结束以后需要进行一个销毁的操作【否则会造成内存泄漏】
❗️(int*)malloc(sizeof(int))查阅资料
(int*)malloc(20*sizeof(int))意思是申请一段长度为20个int型的内存空间
引用:C语言(int *)malloc(sizeof(int))的作用与意思
引用:malloc(20*sizeof(int))
c 和 c++ 的内存管理机制
c内存管理
用malloc/free动态分配内存
红 临时变量存在栈里
蓝 malloc calloc realloc会放在堆里
粉 全局变量放在数据段里
紫 只读常量放在代码段里
c++内存管理
用new/delete分配内存
- malloc/free 只是动态分配内存空间/释放空间 new/delete 还会调用构造构造函数和析构函数进行初始化与清理
- malloc/free 需要手动计算类型大小 new/delete 可自动计算类型大小
- malloc/free 管理内存失败会返回0 new/delete 管理内存失败会抛出异常
智能指针 Smart Pointer
保留 -> 和 * 运算符
auto_pt&unique_ptr两个指针不能指向同一个资源
无法进行左值赋值构造
例子:创建了p1和p2分别指向内存里"I'm Li Ming!"和"I'm age 22."的两个String对象
当我们想把p2赋值给p1时编译器会报错
把p2传进去给p3,完成一个初始化构建也不行shared_ptr可以复制赋值操作,可以多个指针指向同一个内存
对象+引用计数的概念
当通过
shared_ptr进行构造的时候,它不仅构造了对象本身(T Object),还会在内存里构造一个Control Block,里面存储了引用计数、弱引用计数
引用计数 Reference Counting
记录了当前内存资源到底有多少指针在引用(可以访问这个资源)
当新增加一个可以访问这个资源的引用,计数器会加1(Copy),反之会减去1
当引用计数=0时,对象会被销毁\
例子:
通过
shared_ptr有个sp1和sp2,让sp2指向一个Person的内存对象
这时,sp1没有指向任何内存对象,所以它的引用计数use_count()为0;sp2指向Person的内存对象,Person的引用计数为1
让sp1也指向sp2指向的Person对象,这时Person有两个值指向它(sp1和sp2),sp1和sp2的use_count()都是Person的引用计数,都是2
Shared_ptr<Person>sp3(sp1);的意思:是sp3也托管Person,所以都是3
shared_ptr<A> sp1(new A(2)); //sp1托管A(2)
shared_ptr<A> sp2(sp1); //sp2也托管A(2)
iOS memory management iOS内存管理机制
核心:管理「(强)引用计数」
任何继承NSObject对象都需要进行内存管理,因为继承NSObject对象会分配在堆里面,需要进行创建、销毁的工作
MRC 手动管理计数【Manual Reference Counting】(不再使用)
alloc,new,copy,mutableCopy开头的方法创建的对象,引用计数 = 1
retain 引用计数 +1
release 引用计数 -1
当对象的引用计数 =0 即销毁,系统会向对象发送dealloc(dealloc不能直接调用)
当一个对象没有被完全释放,那它的引用计数永远>1,该对象会一直占用它被分配到该内存的空间,会导致内存泄漏,进一步会导致内存溢出(out of memory),发生OOM crash,程序崩溃
❗️ARC 自动管理引用计数【Automatic Reference Counting】
如何实现:通过编译器自动加入内存管理代码实现
- retain/release 不用手动写
只需要负责对象的创建
不需要手动维护引用计数,比如retain/release - 只要有一个强指针在内存指向对象,对象就不能释放
ARC销毁时机是强引用的个数为0,而不是引用计数为0 - 默认所有对象变量的指针都是强指针
例子:__weak Person *p2 = [Person new];
在内存里创建一个Person new的对象,有个*p2指针,需要__weak强制定义弱引用
Example (Delegate协议)
自动释放池 Autorelease-Pool
MRC和ARC都拥有
对象放到自动释放池,统一释放
autorelease 马上释放堆某个对象的强引用
release 延迟释放某个对象
在部分场景下,使用Autorelease pool可以降低内存峰值,减少OOM crash发生的情况
❗️Practical Memory Management 应用
@Property 修饰符
@Property不仅生成一个成员变量,还会生成一个set/get方法
@property ... Dog *dog; = -(Dog *)dog;+ -(void)setDog:(Dog *)dog;
属性修饰符会定义不一样的set方法
- Strong和Weak
@property (nonatomic, strong) Dog *dog;
Strong:拥有属性对象:强持有(Dog在heap内存中,只有我销毁了,Dog才能销毁)只有oc对象才能使用该属性
@property (nonatomic, weak) id<PersonDelegate> delegate;
Weak:不拥有属性对象:弱持有(当我这个Person去访问Delegate时,Delegate对象可能已经被销毁了,销毁与我本身无关)只有oc对象才能拥有该属性 - copy
@property (nonatomic, copy) NSString *name;
修饰NSString(不可变对象)
copy:拥有属性对象的拷贝,在set中调用copy方法在操作过程中,set做了把传入值进行copy的操作NSString *name = @"abc"; Person *person = [Person new]; person.name = name;person.name = [name copy]; - assign
@property (nonatomic, assign) int age;
assign:指定setter使用简单赋值,一般用于简单类型,如基本类型(int,bool), NSInteger, CGRect,属于默认(default)修饰符oc对象不可使用
循环引用
Delegate代理模式
代码实现:创建person1, dog1, f1对象,将dog1赋值给person1的dog属性,将f1赋值给person1的代理,f1的朋友指向person1
内存内:
@property (nonatomic, strong) Dog *dog;person1对dog1强引用(实线箭头,+1)
@property (nonatomic, weak) id<PersonDelegate> delegate;person1对f1弱引用(虚线箭头)
@property(nonatomic, strong) Person *myFriend;f1对person1强引用(实线箭头,+1)
当代码运行完,首先f1没有强引用计数指向它(ARC销毁时机是强引用的个数为0),f1销毁;销毁后person1没有强引用计数指向它,person1销毁;随后dog1没有强引用计数指向它,dog1销毁
❗️如果将person1对f1的引用改为strong
@property (nonatomic, strong) id<PersonDelegate> delegate;
会造成person1和f1的相互循环引用,无法销毁,循环引用不允许
Block闭包:下节课重点
浅拷贝 vs 深拷贝,可变 vs 不可变
可变和不可变
| 可变 | 不可变 |
|---|---|
| 数据不会改变 | 数据可能被修改 |
| 线程安全 | 线程不安全 |
| 查找性能好 | 查找性能差 |
| 用于属性声明或方法参数/回传值 | 一般用于方法内的临时变量,可做增删 |
深浅拷贝
| 浅拷贝 | 深拷贝 |
|---|---|
| 指针拷贝,复制新指针 | 内容拷贝 |
| 指向同一块内存区域,实际内存没有拷贝 | 拷贝数据到一块新数据区域指针指向拷贝的新区域 |
以NSString(不可变对象)为例,讨论深浅拷贝
- StringA 和 StringA copy 地址一样:浅拷贝
- StringA 和 StringA mutableCopy 地址不一样:深拷贝
以NSMutableString(可变对象)为例,讨论深浅拷贝
- StringM 和 StringM copy 地址不一样:深拷贝
- StringM 和 StringM mutableCopy 地址不一样:深拷贝
深拷贝一般是在转换可变不可变的时候用到
@property (nonatomic, copy) NSString *name;
//替换copy修饰符为strong修饰符
@property (nonatomic, strong) NSString *strongName;
以NSString(不可变对象)为例,讨论copy vs strong
- 将StringA传到name(copy)上时,地址一样:copy修饰是浅拷贝 -> 对StringA进行copy,属于浅拷贝
- 将StringA传到StrongName(strong)上时,地址一样:strong修饰是浅拷贝 -> 持有StringA
以NSMutableString(可变对象)为例,讨论copy vs strong
- 将StringM传到name(copy)上时,地址不一样:copy修饰是深拷贝 -> 对StringM进行copy,属于深拷贝
- 将StringM传到StrongName(strong)上时,地址一样:strong修饰是浅拷贝 -> 持有StrinM
一般用copy修饰不可变对象:防止传入可变对象,保证持有不可变副本
内存可引起的crash
野指针 Dangling Pointer
当所指向的对象被提前释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍引旧指向已经回收的内存地址
OOM Out Of Memory
设备上应用内存占用过高,被操作系统强制终止(闪退)