1.为什么要学习内存管理
(1)计算机的组成:控制器,运算器,存储器,输入,输出;
可以百科一下,每个组件的功能。
http://baike.baidu.com/link?url=cmbFN0LKCBWNvh22S__H21vtRsXmg5jK_GnoBL3bmZJZIU35Y5vt7U44v0Sk97yYabhA3XNK626wiArvEgznEIhHMX6_bi-67lfz1Dg-5Qm
其中这里的存储器:内存+硬盘;
存储器的功能是存储程序、数据和各种信号、命令等信息,并在需要时提供这些信息。
运算器和控制器结合就是CPU。那么内存就是CPU能直接读写访问数据的地方。
内存对于任何硬件及软件的重要性都是不言而喻的。手机本身的内存也是有限的,所以如何合理的分配内存是很重要的。
我们平时所说的iphone内存是16G的,32G的,这个只是存储部件放在手机里了,我们要讨论的内存管理不是这个。
现在主流的个人手机 内存大概是几个G,但是iPhone 的内存统统是1G.
这里的 1G是运行时数据所在空间。
如此紧凑的内存空间,可以的管理方法可以提高运行的效率,并且不浪费空间。
(2)在网上查找了一些不同语言的内存管理方案
a. java语言:java虚拟机,垃圾回收机制。
b. C++: 相关C++内存管理 相对比较复杂,手动管理
c. Android:如果你懂java,就会更容易理解Android系统的内存管理机制。与java的垃圾回收机制类似,系统有一个规则来回收内存。进行内存调度有个阈值,只有低于这个值系统才会按一个列表来关闭用户不需要的东西。这种内存调度管理方式有个弊端,容易造成内存泄露,所以很多安卓手机经常会出现卡机及提示内存满的状况.
d. ARC编译器管理机制,程序员在使用Xcode 4.2以上版本进行软件开发时,编译器自动在相应的位置进行内存释放,不需要手动管理。
(3) C 和 C++内存管理的不足
(4)C语言的内存管理结构
对于C语言来说,内存空间主要由5个部分组成
a 栈(stack):是用户存放程序临时创建的局部变量,不包括static声明的变量。一般都是函数中定义的变量。函数被调用的时候,参数会被圧入发起调用的进程栈中,并且等到调用结束,函数的返回值也会被存放都栈中。栈的特点:先近先出。所以特别方便用来保存/恢复调用现场。所以我们可以把栈看成一个寄存、交换临时数据的内存区。
b. 堆 (heap)用于存放进程运行中被动态分配的内存段,它的大小不固定,可以动态扩张或者缩减。当进程调用malloc等函数分配内存的时候,新分配的内存就被动态添加到堆上。当使用了free等函数释放内存的时候,被释放的内存从堆中被剔除。
c. BSS段:存放到程序中的未初始化的全局变量和静态变量。(这里注意一个问题:一般的书上都会说全局变量和静态变量是会自动初始化的,那么哪来的未初始化的变量呢?变量的初始化可以分为显示初始化和隐式初始化,全局变量和静态变量如果程序员自己不初始化的话的确也会被初始化,那就是不管什么类型都初始化为0,这种没有显示初始化的就是我们这里所说的未初始化。既然都是0那么就没必要把每个0都存储起来,从而节省磁盘空间,这是BSS的主要作用)的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。 BSS节不包含任何数据,只是简单的维护开始和结束的地址,即总大小,以便内存区能在运行时分配并被有效地清零。BSS节在应用程序的二进制映象文件中并不存在,即不占用磁盘空间而只在运行的时候占用内存空间,所以如果全局变量和静态变量未初始化那么其可执行文件要小很多。
d. 数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。 字符串常量等,但一般都是放在只读数据段中。
e. 代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中。
以上关于c的内存管理
有了以上几种语言的内存管理方案的提出的优缺点,也得出了,OC是基于C的,oc的内存管理要在C的基础上有所改善。
2 . OC的内存管理
所谓的内存管理就是对内存进行管理。
1) 涉及到的操作有
(1)分配内存:创建一个对象会增加内存的占用;
(2)清楚内存:销毁一个对象能减少内存的占用。
2)内存管理的范围
(1)任何继承了NSObject的对象;
(2)对其他非对象类型无效(int float double struct enum等)
3)为什么OC的内存管理只是多OC对象的管理
因为OC的对象存放到堆里面;非OC对象一般放在栈里面(栈内存会被系统自动回收)
4)几个常用术语的理解
(1)栈 (操作系统)
由操作系统自动分配和释放的,存放函数的参数值,局部变量的值。操作的方式也类似于数据结构中的栈(先进先出)
(2)堆 (操作系统)
一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表。
(3)僵尸对象
已经被销毁的对象(不能再使用的对象)
(4)野指针
指向僵尸对象(不可用内存)的指针。给野指针发消息会报EXC_BAD_ACCESS错误。为了避免野指针错误的常见办法。在对象被销毁之后, 将指向对象的指针变为空指针
(5)空指针
没有指向存储空间的指针(里面存的是nil, 也就是0)。给空指针发消息是没有任何反应的
5)内存管理的基本思想
(1)OC的内存管理,也就是引用计数的内存管理。
引用计数表示的是有多少人在使用这个对象。当没有人使用这个对象的时候,系统才会收回这个对象。此时的引用计数为0.当引用计数不是0的时候,它占用的内存也不会被释放。
(2)引用计数器的操作
当使用alloc,new,copy(mutableCopy)创建一个对象的时候,对象的引用计数默认为1.;当给对象发送了一个retain消息的时候,可以使用引用计数器的值+1; 当给对象发送一个release消息,可以使引用计数器的值-1(明确一个问题就是release不是用来销毁对象的。仅仅是计数器-1);给对象发送retainCount可以获取当前对象的引用计数。
(3)dealloc方法
当一个对象的引用计数值为0的时候,这个对象就会被销毁,其占用的内存被系统回收,对象即将被销毁的时候,系统会自动给对象发送一条dealloc消息。这是会调用dealloc方法。
-(void)delloc{
[super delloc]; }
重写dealloc方法的时候,就必须调用[super dealloc] 并且放在最后面调用
6)内存管理的规则
自己生成的对象,自己持有。
非自己生成的对象,自己也能持有;
不再需要自己持有的对象时释放;
非自己持有的对象无法释放;
(1)OC对对象的操作的状态:
a. 生成并持有对象:alloc,new,copy,mutableCopy等;
b. 持有对象:retain方法
c. 释放对象:release方法
d. 废弃对象:dealloc方法
OC的内存管理方法不包含在这个语言中,而是保护在Cocoa框架中,用于OS X, iOS应用的开发。Cocoa 框架中的Foundation 框架类库的NSObject类担负内存管理的职责。
(1.a)自己生成的对象,自己持有
使用alloc,new,copy, mutableCopy创建的对象。自己持有该对象。
对于自己持有的对象,才能释放(也就是可以调用release方法)非自己持有的对象,不能调用这个方法。
(1.b) 非自己生成的对象,自己也能持有
取得对象不是通过 alloc,new,copy, mutableCopy 这些方法的到的,而是通过一些类的类方法的到的。我们可以通过retain获取持有权。
id obj = [NSMutableArray array];
[obj release];
NSLog(@"obj---%ld",[obj retainCount]);
id object = [[NSMutableArray alloc]init];
[object release];
NSLog(@"object---%ld",[object retainCount]);
MRCDeom[11254:191042] obj---1
MRCDeom[11254:191042] object---1
程序运行结束互闪退了。原因是 obj并不持有 NSMutableArray生成的对象。没有release对象的权利。
id obj = [NSMutableArray array];
[obj retain];
[obj release];
NSLog(@"obj---%ld",[obj retainCount]);
id object = [[NSMutableArray alloc]init];
[object release];
NSLog(@"object---%ld",[object retainCount]);
MRCDeom[11565:197111] obj---1
MRCDeom[11565:197111] object---1
让obj先执行一次 [obj retain]; 我们可以发现obj在release的时候不会崩溃了。
从引用计数上看,不能看出持有和非持有对象的差距。obj 取得对象时,引用计数就是1,retain之后就是 2.
通过retain方法。非自己生成的对象跟用 alloc,new,copy,mutableCopy。方法生成并持有的对象一样,成为自己所持有的。
(1.c) 不再需要自己持有的对象时释放
自己持有的对象,一旦不在需要,持有者有义务释放该对象。释放使用release。
id obj = [NSMutableArray array];
[obj retain];
[obj release];
NSLog(@"obj---%ld",[obj retainCount]);
id object = [[NSMutableArray alloc]init];
[object release];
NSLog(@"object---%ld",[object retainCount]);
NSLog(@"count---%ld",[object count]);
MRCDeom[11983:203875] obj---1
MRCDeom[11983:203875] object---1
使用alloc方法创建的object对象。release后,执行对象的指针仍然被保留在变量object中,这时候接着去访问object对象[object count] 这时会报错EXC_BAD_ACCESS,这个指针成了野指针。这个问题的处理方案
id object = [[NSMutableArray alloc]init];
[object release];
object = nil;
NSLog(@"object---%ld",[object retainCount]);
NSLog(@"count---%ld",[object count]);
MRCDeom[12111:206882] object---0
MRCDeom[12111:206882] count---0
这里的处理方法就是 object = nil;这样objec就是个空指针了。对象被释放了,指针也设置为了空指针所以这里的引用计数就是0了。
对于非自己生成的对象,自己要先获取持有权,才能去释放。
特殊:如果一个方法生成了一个对象,然后返还给方法的调用者。该如何去释放这个对象?
- (id)allocObject{
id object = [[NSObject alloc]init];
return object;
}
- (id)object{
id object = [[NSObject alloc]init];
[object autorelease];
return object;
}
id objOne = objOne = [test allocObject];
[objOne release];
NSLog(@"objOne--%ld",[objOne retainCount]);
id obj = [test object];
[obj release];
NSLog(@"obj----%ld",[obj retainCount]);
MRCDeom[12741:216752] objOne--1
MRCDeom[12741:216752] obj----1
程序崩溃了。
objOne 调用allObject的方法,对返回的对象是 生成并持有的权限。
obj 调用的是 object的方法,对返回的对象是 取得并不持有。
所以obj调用了release的方法就会崩溃。
根据“谁生成,谁释放”的原则。所以我们一般采用 object的方法。在方法中创建对象,就直接 使用autoRelease。(后面会更加详细的讲解)
(1.d)非自己持有的对象无法释放;
通过上面的例子这个就很好理解了。
在我看的书《Object-C高级编程 iOS与OS X多线程和内存管理》这本书中,还详细的讲解了alloc,retain,release,dealloc的内部实现原理。
3 autorelease
(1)对autorelease的理解
autorelease是一种支持引用计数的内存管理方式。
它可以暂时的保存某个对象(object),然后在内存池自己的排干(drain)的时候对其中的每个对象发送release消息.
Autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当 前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release。
注意,这里只是发送release消息,如果当时的引用计数(reference-counted)依然不为0,则该对象依然不会被释放。可以用该方法来保存某个对象,也要注意保存之后要释放该对象。
(2) 为什么使用autorelease
使用原因是上面的一个例子引出来的;
(3) autorelease 的具体使用方法
a. 生成并持有NSAutoreleasePool对象;
b. 调用已分配对象的autorelease实例方法;
c. 废弃NSAutoreleasePool对象;
d. 对于autorelease pool本身,会在如下两个条件发生时候被释放:手动释放Autorelease pool;Runloop结束后自动释放
MRC
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
[pool release];
ARC
@autoreleasepool { }
(3)应用
a. 在iOS程序中有个默认的AutoreleasePool,程序开始创建时创建,退出的时候销毁。对于每个runloop 系统都隐式的创建一个autorelease pool;这样就会构建一个栈式结构 当每一个Runloop结束是,当前栈顶的autoreleasepool就会被销毁
b. 当我们在程序中需要做一些大数据的操作的时候,例如 读取大量的照片,这样会产生很多了autorelease。这种情况下需要创建一个pool,把autorelease的照片放到 pool中。
c. autoreleasepool 来避免频繁申请、释放内存。
(4)注意事项:这里本人没有做出实际的操作;
a. NSAutoreleasePool可以创建一个autorelease pool,但该对象本身也需要被释放
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
[pool release];
使用[pool release]或[pool drain]效果是相同的
b. 在ARC下,不能使用上述方式调用autorelease,而应当使用@autoreleasepool,如
@autoreleasepool { }
这个是读取手机相册的图片的Dmeo,读取大量的照片,可以放到一个自动释放池中,离开这个作用域后方便清除
@autoreleasepool {
[self getOriginalImages];
[self.collectionView reloadData];
}
c. 尽量避免对大内存使用该方法,如图片。对于这种延迟释放机制,还是尽量少用,
d. 不要把大量循环操作放到同一个NSAutoreleasePool之间,这样会造成内存峰值的上升。
e. 对于不同线程,应当创建自己的autorelease pool。如果应用长期存在,应该定期drain和创建新的autorelease pool
f.如果不是使用的NSThread,就不要用aoturelease pool,除非你是多线程模式(multithreading mode) ,可以使用NSThread的isMultiThreaded方法测试你的应用是否是多线程模式
在我看的书《Object-C高级编程 iOS与OS X多线程和内存管理》这本书中,还详细的讲解了autorelease 的内部实现原理。
下一节 总结 ARC