如果有不好的地方或者不全面的地方请留言批评指正,拜谢~~~
引发反思
栈怎么清除?会引发什么状况?怎么使栈溢出?
堆空间怎么清除?会引发什么状况?怎么使堆溢出?
Class A{
NSString *_name;
NSString *_xxxx;
int _age;
A *superClass;
}
A *a = [[A alloc] init];
内存是这么分配的?
a代表什么?
内存管理总结
内容分为虚拟内存+物理内存,正常初始化空间分配的都是虚拟内存,初始化实例才会占用物理内容
Instrument的Allocations工具可以监控
All Heap & Anoymous VM代表虚拟内存
Dirty Memory,resident Size就是物理内存的大小
访问内存其实都是访问的逻辑地址,需要转换后才能访问物理内存,
CPU根据逻辑地址通过界限寄存器判断是否越界,越界地址错误,反之加上基址寄存器转换成物理内存地址
iOS内存过多而被Kill掉?基于什么原则?
iOS使用的是低内存处理机制Jetsam,基于优先级队列的机制。
当内存过低的时候,就会在队列中进行广播,希望大家尽量释放内存,如果一段时间后,仍然内存不够,就会开始Kill进程,直到内存够用。
介绍下内存的几大区域?
1、栈:局部变量(基本数据类型、指针变量)当期作用域执行完毕之后,就会被系统立即收回(无需程序员管理(分配地址由高到低分配))
2、堆:程序运行的过程中动态分配的存储空间(创建的对象),手动申请的字节空间需要调用free来释放
3、Bss段:没有初始化的全局变量和静态变量,一旦初始化就会从BSS段中收回掉,转存到数据段中
4、数据段:存放已经初始化的全局变量和静态变量,以及常量数据,直到程序结束才会被立即收回
5、代码段:程序编译后的代码内容,直到结束程序才会被收回
iOS内存中的对象主要有两类,
一类是值类型,比如int、float、struct等基本数据类型
一类是引用类型,也就是继承NSObject类的所有OC对象
值类型会被放入栈中,遵循先进后出的原则
引用类型会放在堆中,当给对象分配内存空间时,对随机在内存中开辟空间。
为什么会循环引用
当两个不同的对象各有一个强引用指向对方,那么循环引用就产生了,每个对象的引用计数都会+1,无法得到内存的释放
weak是弱引用,计数器不会加一,并在引用对象被释放的时候自动设置为nil
unsafe_unretained , weak, assign 区别
- __unsafe_unretained: 不会对对象进行retain,当对象销毁时,会依然指向之前的内存空间(野指针)
- __weak: 不会对对象进行retain,当对象销毁时,会自动指向nil
- assign:用于对基本数据类型进行赋值操作,不更改引用计数(基本类型内存分配栈上,系统自动处理)。如果来修饰对象,被assgin修饰的对象在释放后,指针的地址还是存在的,成为野指针。如果后续分配在堆上的内存正好在这个地址上程序就会crash。
- copy:在修饰Mutable可变类型会在内存里拷贝一份对象,两个指针指向不同的内存地址。copy出来的新对象是不可变类型的。而修饰NSString、NSArray等普通类型,充当strong使用,内存计数+1
- strong:强指针,指向对象内存地址,内存计数+1
- nonatomic:非原子属性。它的特点是多线程并发访问性能高,但是访问不安全
- atomic:原子性,线程安全的,setter方法加锁
野指针是什么,iOS 开发中什么情况下会有野指针?
指针是不为nil,但是指向已经被释放的内存的指针。
__unsafe_unretain或者assign的指针,对象释放后会出现野指针。
一般情况下oc使用了weak指针,在对象销毁时指针会置nil
block内存相关
问:__block什么时候用?
答:在block里面修改局部变量的值都要用__block修饰
问:在block里面, 对数组执行添加操作, 这个数组需要声明成__block吗?
答:不需要声明成__block,因为testArr数组的指针并没有变(往数组里面添加对象,指针是没变的,只是指针指向的内存里面的内容变了)
问:在block里面, 对NSInteger进行修改, 这个NSInteger是否需要声明成__blcok ?
答:NSInteger的值发生改变,则要求添加__block修饰
block变量定义时为什么用copy?block是放在哪里的?
默认情况下,block是存档在栈中,可能被随时回收,通过copy操作可以使其在堆中保留一份, 相当于一直强引用着, 因此如果block中用到self时, 需要将其弱化, 通过__weak或者__unsafe_unretained
autoreleasepool的使用场景和原理
自动释放池是OC中内存自动回收机制,它可以延迟加入autoreleasepool中变量release的时机,创建的变量会在超出其作用域的时候release
autorelease本质上就是延迟调用release
在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop(每一个线程都有一个默认autoreleasepool)
栈 - 泄露
int number = 4;
int *a = malloc(8);
a = &number;
free(a);
给指针a分配了8个字节的地址,a又指向了number的地址,最后a释放了。这时候释放的是number的地址,而number是在栈区中,不能被手动释放,这时候就出现了栈的内存泄露
weak、strong在内存的作用
默认情况下,一个指针都会使用__strong属性,表明这是一个强引用。当所有强引用都去除时,对象才能被释放
但有时候我们可能要禁止这种行为:一些集合类不应该增加其元素的引用,可能对导致对象无法释放,可能会出现循环引用,导致无法释放,这种情况下我们要使用弱引用__weak。
weak是弱引用,计数器不会加一,并在引用对象被释放的时候自动设置为nil
线程中栈与堆是公有的还是私有的
在多线程环境下,每个线程拥有一个栈和程序计数器,栈和程序计数器用来保存线程执行历史和线程执行的状态,是线程私有资源
堆资源是统一进程内多线程共享的
OC的内存是怎么管理的?
一般来说我们可能会说“使用引用计数管理”,是的没错,但是引用计数是如何管理的呢?
2013年9月,苹果推出了iPhone5s,推出TaggedPointer概念,为了节省内存和提高执行效率。
NSNumber,NSString,NSData等一些较小的数据TaggedPointer将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址
正常Object对象还是会使用引用计数来管理,那么苹果是这么管理的呢?
为了管理内存苹果内置了全局的SideTables(散列表),存储的是SideTable的结构体。
SideTable 结构体
struct SideTable{
//保证原子操作的自旋锁
spinlock_t slock;
//引用计数的hash表
RefcountMap refcnts;
//weak 引用全局hash表
weak_table_t weak_table;
}
网上有个例子这里:
100个学生(对象)住宿,大家都在一个楼上(SideTables),有10个房间(SideTable),每个房间8个学生(obj);
关系类似于此
spinlock_t:自旋锁,如果已经在访问100号宿舍的某个学生,那么在这时候是其他线程是无法访问100号宿舍的其他学生,自旋锁比较适用于锁使用者保持锁时间比较短的情况
RefcountMap:对象具体的引用计数,没错就是他,所有strong等可以是引用计数+1的操作都会在这里标记,
因为一个SideTable可能有多个对象的计数器,SideTables[0x0000]和SideTables[0x0x000f] 可能都是同一个SideTable,所以苹果又提供了table.refcnts.find[0x0x000f]来找到真正的引用计数器
计数器的存储结构:
)
看图表示可以得知:真正引用计数器是从第三位开始,也就是4的位置。
如果这里引用计数为0了,就会直接执行dealloc,查看是否有weak引用会把weak_table_t中的弱引用置nil
weak_table_t:
struct weak_table_t {
// 保存了所有指向指定对象的 weak 指针(数组)
weak_entry_t *weak_entries;
// 存储空间
size_t num_entries;
// 参与判断引用计数辅助量
uintptr_t mask;
// hash key 最大偏移值
uintptr_t max_hash_displacement;
};
weak_entries:通过循环遍历来找到对应的entry。
struct weak_entry_t {
//被指对象的地址。前面循环遍历查找的时候就是判断目标地址是否和他相等
DisguisedPtrobjc_object> referent;
union {
struct {
//可变数组,里面保存着所有指向这个对象的弱引用的地址。当这个对象被释放的时候,referrers里的所有指针都会被设置成nil。
weak_referrer_t *referrers;
uintptr_t out_of_line : 1;
uintptr_t num_refs : PTR_MINUS_1;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
//只有WEAK_INLINE_COUNT个元素的数组,默认情况下用它来存储弱引用的指针。当大于4个的时候使用referrers来存储指针。
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
}
}
weak的原理
为了管理所有的引用计数和weak指针,苹果创建了一个全局的SideTables,它是一个全局的hash表,用于存储指向某个对象的所有weak指针,key是所指向对象的地址,value是weak指针的地址数组,里面存放的都是SideTable结构体
weak是弱引用,计数器不会加一,并在引用对象被释放的时候自动设置为nil
对象引用计数相关的操作是原子性的,如果多个线程同事操作一个对象的引用计数会造成数据错乱,同时在内存中的对象数据量大,不能读整个Hash加锁,所以苹果采用了分离锁
步骤:
1、初始化时,runtime会调用obj_initWeak函数,初始化一个新的weak指针指向对象的地址
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
初始化weak变量的时候,runtime会调用NSObject.mm中的obj_initWeak函数,函数声明如下:
id objc_initWeak(id *object, id value);
而对于objc_initweak()方法的实现
id objc_initWeak(id *location, id newObj) {
// 查看对象实例是否有效
// 无效对象直接导致指针释放
if (!newObj) {
*location = nil;
return nil;
}
// 这里传递了三个 bool 数值
// 使用 template 进行常量参数传递是为了优化性能
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
2、添加引用时,obj_initWeak函数会调用obj_storeWeak()函数更新指针指向,创建对应的弱引用表
obj_storeWeak()函数声明
id objc_storeWeak(id *location, id value);
代码具体看:
http://www.cocoachina.com/ios/20170328/18962.html
https://www.desgard.com/weak/
3、释放时,调用clearDeallocating函数,首先根据对象地址获取到所有的weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录
http://www.cocoachina.com/ios/20170328/18962.html
相关链接:
http://sindrilin.com/runtime/2016/12/23/闲聊内存管理
http://zhoulingyu.com/2017/02/15/Advanced-iOS-Study-objc-Memory-2/
https://www.aliyun.com/jiaocheng/topic_39068.html
http://www.cocoachina.com/ios/20150605/11990.html
http://ios.jobbole.com/89012/