Objective-c 内存溢出问题经验汇总

iOS平台的内存使用引用计数的机制,并且引入了半自动释放机制;这种使用上的多样性,导致开发者在内存使用上非常容易出现内存泄漏和内存莫名的增 长情况; 本文会介绍iOS平台的内存使用原则与使用陷阱; 深度剖析autorelease机制;低内存报警后的处理流程;并结合自身实例介绍内存暴增的问题追查记录以及相关工具的使用情况;iOS平台内存常见问题作为iOS平台的开发者,是否曾经为内存问题而苦恼过?内存莫名的持续增长,程序莫名的 crash,难以发现的内存泄漏,这些都是iOS平台内存相关的常见问题;本文将会详细介绍iOS平台的内存管理机制,autorelease机制和内存 的使用陷阱,这些将会解决iOS平台内存上的大部分问题,提高了程序的稳定性;1 iOS平台内存管理介绍 iOS平台的内存管理采用引用计数的机制;当创建一个对象时使用alloc或者allWithZone方法时,引用计数就会+1;当释放对象使用release方法时,引用计数就是-1; 这就意味着每一个对象都会跟踪有多少其他对象引用它,一旦引用计数为0,该对象的内存就会被释放掉;另外,iOS也提供了一种延时释放的机制 AutoRelease,以这种方式申请的内存,开发者无需手动释放,系统会在某一时机释放该内存; 由于iOS平台的这种内存管理的多样性,导致开发者在内存使用上很容易出现内存泄漏或者程序莫名崩溃的情况,本文会详细介绍iOS平台内存的使用规范与技 巧以及如何利用工具避免或者发现问题;2 iOS平台内存使用原则2.1 对象的所有权与销毁2.1.1 谁创建,谁释放; 如果是以alloc,new或者copy,mutableCopy创建的对象,则必须调用release或者autorelease方法释放内存;如果没有释放,则导致内存泄漏!2.1.2 谁retain,谁释放; 如果对一个对象发送 retain消息,其引用计数会+1,则使用完必须发送release或者autorelease方法释放内存或恢复引用计数;如果没有释放,则导致内存泄漏!2.1.3 没创建且没retain,别释放; 不要释放那些不是自己alloc或者retain的对象,否则程序会crash;不要释放autorelease的对象,否则程序会crash;2.2 对象的深拷贝与浅拷贝 一般来说,复制一个对象包括创建一个新的实例,并以原始对象中的值初始化这个新的实例。 复制非指针型实例变量的值很简单,比如布尔,整数和浮点数。复制指 针型实例变量有两种方法。一种方法称为浅拷贝,即将原始对象的指针值复制到副本中。因此,原始对象和副本共享引用数据。另一种方法称为深拷贝,即复制指针 所引用的数据,并将其赋给副本的实例变量。2.2.1 深拷贝 深拷贝的流程是 先创建一个新的对象且引用计数为1,并用旧对象的值初始化这个新对象;ClassA* objA = [[ClassA alloc] init];ClassA* objB = [objA copy];objB是一个新对象,引用计数为1,且objB的数据等同objA的数据;注意: objB需要释放,否则会引起内存泄漏!2.2.2 浅拷贝 浅拷贝的流程是,无需引入新的对象,把原有对象的引用计数+1即可ClassA* objA = [[ClassA alloc] init];ClassA* objB = [objA retain];注意: objB需要释放,恢复objA的引用计数,否则会引起内存泄漏!2.3对象的存取方法2.3.1 属性声明和实现 变量声明的常用属性类型包括readonly,assign,retain和copy;且系统会自动为声明了属性的变量生成set和get函数;readonly属性: 只能读,不能写;assign属性: 是默认属性,直接赋值,没有任何保留与释放问题;retain属性: 会增加原有对象的引用计数并且在赋值前会释放原有对象,然后在进行赋值;copy属性:  会复制原有对象,并在赋值前释放原有对象,然后在进行赋值;2.3.2 使用属性声明可能带来的隐患 当一个非指针变量使用retain(或者copy)这个属性时,尽量不要显性的release这个变量;直接给这个变量置空即可;否则容易产生过度释放,导致程序crash; 例如:ClassA类的strName是NSString* 类型的变量且声明的属性为retain;ClassA.strName = nil;  /* 释放原有对象且对此对象赋值为空 */[ClassA.strName release]; /* strName内存可能已经被释放过了,将导致程序crash */Assign这个属性一般是非指针变量布尔类型,整形等)时用这个类型;属于直接赋值型,不需要考虑内存的保留与释放;如果一个指针类型的变量使用assign类型的属性,有可能引用已经释放的变量;导致程序crash; 例如:ClassB* obj =[[[ClassB alloc] init] autorelease];……ClassA.strName = obj; /* strName 指向obj的内存地址*/后续在使用ClassA.strName的时候, 因为obj是autorelease的,可能obj的内存已经被回收;导致引用无效内存,程序crash;3iOS平台AutoRelease机制3.1 自动释放池的常见问题 大家在开发iOS程序的时候,是否遇到过在列表滑动的情况内存莫名的增长,频繁访问图片的时候内存莫名的增长,频繁的打开和关闭数据库的时候内存莫名的增长…… 这些都是拜iOS的autorelease机制所赐;具体分析如下:1: 滑动列表的时候,内存出现莫名的增长,原因可能有如下可能:1:没有使用UITableView的reuse机制; 导致每显示一个cell都用autorelease的方式重新alloc一次; 导致cell的内存不断的增加;2:每个cell会显示一个单独的UIView, 在UIView发生内存泄漏,导致cell的内存不断增长;2: 频繁访问图片的时候,内存莫名的增长;频繁的访问网络图片,导致iOS内部API,会不断的分配autorelease方式的buffer来处理图片的解码与显示; 利用图片cache可以缓解一下此问题;3: 频繁打开和关闭SQLite,导致内存不断的增长;在进行SQLite频繁打开和关闭操作,而且读写的数据buffer较大,那么 SQLite在每次打开与关闭的时候,都会利用autorelease的方式分配51K的内存; 如果访问次数很多,内存马上就会顶到几十兆,甚至上百兆! 所以针对频繁的读写数据库且数据buffer较大的情况, 可以设置SQLite的长连接方式;避免频繁的打开和关闭数据库;3.2 自动释放池的概念 NSAutoreleasePool内部包含一个数组(NSMutableArray),用来保存声名为autorelease的所有对象。如果一个对象声明为autorelease,系统所做的工作就是把这个对象加入到这个数组中去。ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此对象加入autorelease pool中NSAutoreleasePool自身在销毁的时候,会遍历一遍这个数 组,release数组中的每个成员。如果此时数组中成员的retain count为1,那么release之后,retain count为0,对象正式被销毁。如果此时数组中成员的retain count大于1,那么release之后,retain count大于0,此对象依然没有被销毁,内存泄露。3.3 自动释放池的作用域与嵌套 AutoreleasePool是可以嵌套使用的!池是被嵌套的,嵌套的结果是个栈,同一线程只有当前栈顶pool实例是可用的:

当短生命周期内,比如一个循环中,会产生大量的临时内存,可以创建一个临时的autorelease pool,这样可以达到快速回收内存的目的;3.4 自动施放池的手动创建与自动创建3.4.1 需要手动创建自动释放池●如果你正在编写一个不是基于Application Kit的程序,比如命令行工具,则没有对自动释放池的内置支持;你必须自己创建它们。●如果你生成了一个从属线程,则一旦该线程开始执行,你必须立即创建你自己的自动释放池;否则,你将会泄漏对象。●如果你编写了一个循环,其中创建了许多临时对象,你可以在循环内部创建一个自动释放池,以便在下次迭代之前销毁这些对象。这可以帮助减少应用程序的最大内存占用量。3.4.2 系统自动创建自动释放池 Application Kit会在一个事件周期(或事件循环迭代)的开端—比如鼠标按下事件—自动创建一个自动释放池,并且在事件周期的结尾释放它.4 iOS平台内存使用陷阱4.1 重复释放 在前文已经提到,不要释放不是自己创建的对象;释放自己的autorelease对象,app会crash;释放系统的autorelease对象,app会crash;4.2 循环引用  

循环引用,容易产生野引用,内存无法回收,最终导致内存泄漏!可以通过弱引用的方式来打破循环引用链;所谓的弱引用就是不需要retain,直接赋值的方式,这样的话,可以避免循环引用的问题,但是需要注意的是,避免重复释放的问题;5 iOS平台内存报警机制 由于iOS平台的内存管理机制,不支持虚拟内存,所以在内存不足的情况,不会去Ram上 创建虚拟内存;所以一旦出现内存不足的情况,iOS平台会通知所有已经运行的app,不论是前台app还是后台挂起的app,都会收到 memory warning的notice;一旦app收到memory warning的notice,就应该回收占用内存较大的变量;5.1 内存报警处理流程 1: app收到系统发过来的memory warning的notice;2: app释放占用较大的内存;3: 系统回收此app所创建的autorelease的对象;4: app返回到已经打开的页面时,系统重新调用viewdidload方法,view重新加载页面数据;重新显示;5.2 内存报警测试方法 在Simulate上可以模拟低内存报警消息;iOS模拟器 -> 硬件 -> 模拟内存警告;开发者可以在模拟器上来模拟手机上的低内存报警情况,可以避免由于低内存报警引出的app的莫名crash问题;6 iOS平台内存检查工具6.1 编译和分析工具Analyze iOS的分析工具可以发现编译中的warning,内存泄漏隐患,甚至还可以检查出logic上的问题;所以在自测阶段一定要解决Analyze发现的问题,可以避免出现严重的bug;内存泄漏隐患提示:Potential Leak of an object allocated on line ……数据赋值隐患提示:The left operand of …… is a garbage value;对象引用隐患提示:Reference-Counted object is used after it is released;以上提示均比较严重,可能会引起严重问题,需要开发者密切关注!6.2 内存检测工具6.2.1 内存泄漏检测工具—Leak Leak工具可以很容易的统计所有内存泄漏的点,而且还可以显示在那个文件,哪行代码有 内存泄漏,这样定位问题比较容易,也比较方面;但是Leak在统计内存泄漏的时候会把autorelease方式的内存也统计进来; 所以我们在查找内存泄漏情况的时候,可以autorelease的情况忽略掉;Leak工具:

通过Leak工具可以很快发现代码中的内存泄漏,通过工具也可以很快找到发生内存泄漏的代码段:

6.2.2 内存猛增检测工具—Allocations Allocations工具可以很容易的列出所有分配内存的点,这样我们可以按照分配内存大小来进行排序, 这样可以很容易的发现哪些点分配的内存最多,而且是持续分配,这样我们来针对性的分析这些持续分配较大内存的地方;

此工具会显示出所有申请内存的地方,并统计申请的次数和大小; 从这个列表中可以找出内存申请次数最多且申请内存最大的语句;从而分析出哪些地方使用的内存最多,进而可以优化和改进;
现在大概知道有以下几种:1 非ARC模式下, release忘了.2 少用[UIImage imageNamed]3 后台线程的时候,忘了加AutoReleasePool
时间: 2024-08-29 17:26:51

Objective-c 内存溢出问题经验汇总的相关文章

java内存区域/内存溢出汇总

本文主要介绍Java虚拟机中的内存区域与各种内存溢出情况汇总. 数据区域 方法区.堆.虚拟机栈.程序计数器.本地方法栈 方法区 用于存储已被虚拟机加载的类信息.常量.静态变量.即时编译器编译后的代码 运行时常量池:存放编译期生成的字面量和符号引用 异常(OutofMemoryError: PermGen space)产生原因:1.往常量池中添加大量数据.eg:String.intern()2.大量的类信息(或者动态代理) 异常处理:适当增大配置:-XX:PermSize -XX:MaxPermS

内存溢出的预防及解决汇总

内存溢出是一个非常隐式的问题,经过相关资料的查询,先总结一下内存溢出的关键字:what(溢出表现).origin(内存分配及回收).where(溢出类型).why(泄露原因).how(预防及解决方案). 一.what(溢出表现) 1. 服务器内存长期不合理占用,内存经常处于高位占用,很难回收到低位: 2. 服务器极为不稳定,几乎每两天重新启动一次,有时甚至每天重新启动一次: 3. 服务器经常做 Full GC(Garbage Collection),而且时间很长,大约需要 30-40秒,应用服务

Android加载图片导致内存溢出(Out of Memory异常)

Android在加载大背景图或者大量图片时,经常导致内存溢出(Out of Memory  Error),本文根据我处理这些问题的经历及其它开发者的经验,整理解决方案如下(部分代码及文字出处无法考证):  方案一.读取图片时注意方法的调用,适当压缩  尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗

Android学习笔记_78_ Android开发中使用软引用和弱引用防止内存溢出

在<Effective Java 2nd Edition>中,第6条"消除过期的对象引用"提到,虽然Java有 垃圾回收机制,但是只要是自己管理的内存,就应该警惕内存泄露的问题,例如的对象池.缓存中的过期对象都有可能引发内存泄露的问题.书中还提到可以用 WeakHashMap来作为缓存的容器可以有效解决这一问题.之前也确实遇到过类似问题,但是没有接触过"弱引用"相关的问题,于是查阅了一些资料. <Java 理论与实践: 用弱引用堵住内存泄漏>

Java中OutOfMemoryError(内存溢出)的三种情况及解决办法

相信有一定java开发经验的人或多或少都会遇到OutOfMemoryError的问题,这个问题曾困扰了我很长时间,随着解决各类问题经验的积累以及对问题根源的探索,终于有了一个比较深入的认识. 在解决java内存溢出问题之前,需要对jvm(java虚拟机)的内存管理有一定的认识.jvm管理的内存大致包括三种不同类型的内存区域:Permanent Generation space(永久保存区域).Heap space(堆区域).Java Stacks(Java栈).其中永久保存区域主要存放Class

图片--Android加载图片导致内存溢出(Out of Memory异常)

Android在加载大背景图或者大量图片时,经常导致内存溢出(Out of Memory  Error),本文根据我处理这些问题的经历及其它开发者的经验,整理解决方案如下(部分代码及文字出处无法考证):  方案一.读取图片时注意方法的调用,适当压缩  尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗

[转]Java内存溢出详解及解决方案

原文地址:http://blog.csdn.net/xianmiao2009/article/details/49254391 内存溢出与数据库锁表的问题,可以说是开发人员的噩梦,一般的程序异常,总是可以知道在什么时候或是在什么操作步骤上出现了异常,而且根据堆栈信息也很容易定位到程序中是某处出现了问题.内存溢出与锁表则不然,一般现象是操作一般时间后系统越来越慢,直到死机,但并不能明确是在什么操作上出现的,发生的时间点也没有规律,查看日志或查看数据库也不能定位出问题的代码. 更严重的是内存溢出与数

文件上传之--内存溢出(System.OutOfMemoryException)

两周前就想把这点经验记录下来了,由于拖延症上身,直到刚才突然想起这件未完成的任务,今天是1024,在这个特别的日子里,祝所有程序猿兄弟姐妹们节日快乐! 上传功能一直很正常,直到上传了个500多兆的文件,报错提示: “System.OutOfMemoryException”类型的异常在 mscorlib.dll 中发生,但未在用户代码中进行处理 对于内部用途和新的托管对象,确保要有足够的内存可供分配.如果要创建数组,请确保大小正确. 由于调用的webservice中的文件上传方法参数为二进制格式,

应用jacob组件造成的内存溢出解决方案(java.lang.OutOfMemoryError: Java heap space)

http://www.educity.cn/wenda/351088.html 使用jacob组件造成的内存溢出解决方案(java.lang.OutOfMemoryError: Java heap space) 都说内存泄漏是C++的通病,内存溢出是Java的硬伤,这个头疼的问题算是让我给碰到了.我在做的这个功能涉及到修改word文档,因为微软没有公开word源代码,所以直接用java流来读取word的后果是读出来的会是乱码,经过查资料得知可以使用poi和jacob来操作word,jacob使用