基本概念
冯·诺依曼结构:运算器 控制器 存储器 输入与输出
内存即存储器,用来存储指令与数据
注:哈佛结构与普林斯顿结构的不同,使用两个独立的存储器模块,分别存储指令和数据,每个存储模块都不允许指令和数据并存;使用独立的两条总线,分别作为CPU与每个存储器之间的专用通信路径,而这两条总线之间毫无关联。
- 物理地址:存储器每一个字节单元给一个唯一的存储器地址,即为物理地址,又叫实际地址或绝对地址。
- 虚拟存储系统:操作系统层面做了一个物理地址与逻辑地址之间的映射。使用虚拟地址,作为读写的一部分。优势如下:
1)程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
2)程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为4KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
3)不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
解决两个问题,一是多个程序同时运行,二是占用很大内存的程序。
一个程序在运行时,实际要用到的指令和数据都是很有限的,不可能从头到尾同时用。那么对于一个程序来说,假装自己有非常大的空间,实际上只要有条理的把暂时要用到的部分放进物理内存供CPU访问就好,这样第二个问题解决了。那既然每个程序(进程)只用一小块,那整个物理内存就可以分给多个程序(进程)用了,第一个问题也迎刃而解。当然,这样做的前提是,数据和指令的动态进出,用完了的暂时不用的踢出内存,需要用的及时加载进来。
- 内存泄漏:动态存储分配的空间,在使用完毕后未释放,结果导致一直占用该内存单元,直到程序结束。
- 内存溢出:没有内存可用。
内存可细分五部分组成
- 代码(指令):静态,就是只读的东西。
- 初始化数据:有初始值的变量,常量。
- 未初始化数据:只声明未给值得变量。
- 栈:程序运行记录,每个线程,也就是每个执行序列各有一个,都是编译时候能确定好的,这里面的数据可以不使用指针也不会丢。
- 堆:最灵活,动态分配和释放,编译时不能确定,OC对象都存在堆里,通常都是用指针访问 指针从线程栈中来,但不独属于某个线程,谁分配谁释放说的是堆上对象的管理。
IOS内存管理
iOS的内存管理也是在以上的大框架下
最大的不同就是物理内存吃紧时的处理
当物理内存吃紧时,IOS会把能通过映射重新加载的内容直接清理出内存,对于不可再生的数据,iOS10需要App进程配合处理,向各进程发送内存警告要求配合释放内存,对于不能及时释放足够内存的,直接kill掉进程,必要时甚至是前台运行的App。
引用计数(reference counting)
OC中每一个对象有一个关联的整数retainCount用于记录对象的使用情况,当retainCount为0时,对象被销毁。
alloc copy new retain 等会使retainCount +1,
release会 -1
NSString 实际上是一个字符型常量,是没有引用计数的
赋值操作是不会拥有对象的,引用计数不会加一,要持有对象需retain
引用计数不为0,因为引用计数为1的对象release时,系统对该对象回收,不做减一操作
基本原则:
自动释放池
不确定一个对象什么时候不再使用
autorelease 自动在未来释放
原理:把对象添加到自动释放池,当自动释放池销魂时,会对池中所有的对象发送release消息。
自动释放池的创建
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
[pool release];-
@autoreleasepool{
//这里写代码
}
注:
1.自动释放池只是给池中所有的对象发送release消息,当对象的引用计数>1时,对象无法销毁。
2.autorelease不会改变对象的引用计数
ARC
ios5 引入ARC(Auto Reference Counting)
编译时特性,不是运行时特性,更不是垃圾回收器
自动引用计数,在编译时,自动加上retain release等,实现内存管理。
ARC开启:可以在工程选项中选择Targets -> Compile Phases -> Compile Sources,在里面找到对应文件,添加flag: -fobjc-arc
关闭:-fno-objc-arc
实际中的使用
ARC IOS5开始引入,现在绝大部分开发都是ARC模式,以下应用主要是针对ARC模式下的应用。
属性的内存管理
1.assign: 一般修饰基本数据类型
2.retain: release旧值,再retain新值
使用set方法,实质上会先保留新值,再释放旧值,再设置新值,避免新旧值一样时导致对象被释放。
MRC写法:
- (void)setCount:(NSObject *)count {
[count retain];
[_count release];
_count = count;
}
ARC写法:
- (void)setCount:(NSObject *)count {
_count = count;
}
3.copy: release旧值,再copy新值(copy内容)
一般修饰 NSString、NSArray、NSDictionary等需要保护其封装性的对象。尤其是在其内容可变的情况下 因此会拷贝一份内容给属性使用,避免可能造成的对源内容进行改动
block一般使用copy修饰
4.weak ARC新引入 可代替assign,比assign多了一个特性(置nil)
delegate 一般用weak修饰,避免循环引用
set方法时,只设置新值
5.strong ARC新引入 可代替retain
block的内存管理
1.循环引用
block一般用copy修饰,当block又引用了对象的其他成员变量时,就会对这个变量本身产生强引用,那么变量本身和它自己的block就形成了循环引用。
__weak typeof(self) weakSelf = self;
ARC下要生成一个对自身的弱引用,表示block别再对self对象retain了,避免循环引用
2.block内部变量
1.block不能改变局部变量,要改变时需加__block修饰
2.block可改变全局变量
3.static变量也可在block中修改
内存问题的分析解决
1.僵尸对象和野指针
僵尸对象:内存被回收的对象
野指针:指向僵尸对象的指针,向野指针发送消息对导致崩溃EXC_BAD_ACCESS
1)在product-scheme-edit scheme-diagnostics中将enable zombie objects勾选上,下次再出现这样的错误就可以准确定位了。
2)在Xcode-open developer tool-Instruments打开工具集,选择Zombies工具可以对已安装的应用进行僵尸对象检测。
2.循环引用
1)在product-Analyze中使用静态分析来检测代码中可能存在循环引用的问题。
2)在Xcode-open developer tool-Instruments打开工具集,选择Leaks工具可以对已安装的应用进行内存泄漏检测,此工具能检测静态分析不会提示,但是到运行时才会出现的内存泄漏问题。
3.循环中对象占用内存大
循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏。
解决方法:在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。
例:
for (int i = 0; i < 10000; i ++) {
@autoreleasepool {
Person * soldier = [[Person alloc]init];
[soldier fight];
}
}
4.内存泄漏
通过Analyze来进行静态代码检查,以发现在语法上显而易见的内存泄露问题
内存泄露是运行时的问题,这时可用Instruments中的Allocation和Leaks来不断重复操作App,发现和定位内存泄露点
5.过度释放
原则上都会直接crash(然而由于某些特殊的情况,不会马上crash)。
对于这种问题,可以直接使用Zombie,当过度释放发生时会立即停在发生问题的位置,同时结合内存分配释放历史和调用栈,可以发现问题。
至于上文提到的不会crash的原因,其实有很多,比如:
1)对象内存释放时,所用内存并没有完全被擦除,仍有旧对象部分数据可用
2)原内存位置被写入同类或同样结构的数据
菜鸟一枚,有错误之处还望大家批评指正!露露