哪些情况会导致内存泄露?如何避免?

2021-01-27 19:24发布

7条回答
梵梵
2楼 · 2021-01-28 09:28

一.资源释放问题:


程序代码的问题,长期保持某些资源,如Context,Cursor,IO流的引用,资源得不到释放造成内存泄漏.


二.对象内存过大:


保存了多个耗用内存过大的对象,如 Bitmap,XML文件,造成内存超出限制。


三.static关键字的使用问题:


static是java中的一个关键字,当用它修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果他用来引用一下资源耗费过多的实例(Context的情况最多),这时就要谨慎对待。

-------针对static的解决方案:1)应该尽量避免static成员变量引用资源耗费过多的实例。如Context。2)Context尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内训泄漏的问题。3)使用WeakReference代替引用,比如可以使用WeakReference mContextRef.


四.线程导致内存溢出:


线程产生内存泄漏的主要原因是在于线程的生命周期不可控。

-------针对这种线程的内存泄漏问题的解决方案:1)将线程的内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。2)在线程内部采用弱引用保存Context的引用。


五.查询数据库没有关闭cursor:


程序中经常用到会进行查询数据库的操作,但是经常会使用完毕Cursor后没有关闭的情况,如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在长时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排查带来困难和风险。


六.构造Adapter没有复用ConvertView:


在使用listview的时候通常使用Adapter,那么 我们应该尽可能的使用ConvertView。为什么要复用ConvertView?当ContertView为空时,用setTag()方法为每一个View绑定一个存放控件的ViewHolder对象,当convertVIew不为空,重复利用已经创建的view的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。


七.Bitmap不在使用时没有调用recycle()释放内存:


无需指教
3楼 · 2021-01-28 09:36

一.资源释放问题:


程序代码的问题,长期保持某些资源,如Context,Cursor,IO流的引用,资源得不到释放造成内存泄漏.


二.对象内存过大:


保存了多个耗用内存过大的对象,如 Bitmap,XML文件,造成内存超出限制。


三.static关键字的使用问题:


static是java中的一个关键字,当用它修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果他用来引用一下资源耗费过多的实例(Context的情况最多),这时就要谨慎对待。

-------针对static的解决方案:1)应该尽量避免static成员变量引用资源耗费过多的实例。如Context。2)Context尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内训泄漏的问题。3)使用WeakReference代替引用,比如可以使用WeakReference mContextRef.


四.线程导致内存溢出:


线程产生内存泄漏的主要原因是在于线程的生命周期不可控。

-------针对这种线程的内存泄漏问题的解决方案:1)将线程的内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。2)在线程内部采用弱引用保存Context的引用。


五.查询数据库没有关闭cursor:


程序中经常用到会进行查询数据库的操作,但是经常会使用完毕Cursor后没有关闭的情况,如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在长时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排查带来困难和风险。


六.构造Adapter没有复用ConvertView:


在使用listview的时候通常使用Adapter,那么 我们应该尽可能的使用ConvertView。为什么要复用ConvertView?当ContertView为空时,用setTag()方法为每一个View绑定一个存放控件的ViewHolder对象,当convertVIew不为空,重复利用已经创建的view的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。


七.Bitmap不在使用时没有调用recycle()释放内存:


有时我们会手动的操作Bitmap对象比较占内存,当它不在被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占的内存,但这不是必须的,视情况而定。



内存泄漏原理:在百度上的解释就是“程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果”。

  • 常见的内存泄漏情况:

    • 情况一:对象之间的循环引用问题
      循环引用的实质:多个对象相互之间有强引用,不能施放让系统回收。解决办法:使用 weak 打破对象之间的相互强引用

    • 情况二:block的循环引用
      blockcopy时都会对block内部用到的对象进行强引用的。解决办法使用:使用__weak打破循环的方法只在 ARC下才有效,在 MRC 下应该使用__block

__weak typeof(self) weakSelf = self; self.myBlock = ^() { // 除了下面的还有 调用 self的一些属性等等 [weakSelf doSomething] };
  • 情况三: delegate 的循环引用
    delegate是委托模式.委托模式是将一件属于委托者做的事情,交给另外一个被委托者来处理,在这里我们可能会出现委托者和被委托人之间的相互强引用问题;解决办法:在声明 delegate 属性的时候 用weak 进行弱引用 或者 通过中间对象(代理对象)的方式来解决(效率更加高的中间对象NSProxy:不需要进行发送消息和再动态解析,直接进行消息转发)

@property(nonatomic, weak) id delegate;
  • 情况四CADisplayLinkNSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用;解决办法:NSTimer 有一个block的方法,我们可以利用block的弱指针来解决__weak typeof(self) weakSelf = self;weakSelf 进去

  • 情况五:通知的循环引用
    iOS9 以后,一般的通知,都不再需要手动移除观察者,系统会自动在dealloc 的时候调用 [[NSNotificationCenter defaultCenter] removeObserver: self]iOS9 以前的需要手动进行移除。原因是:iOS9 以前观察者注册时,通知中心并不会对观察者对象做 retain 操作,而是进行了 unsafe_unretained 引用,所以在观察者被回收的时候,如果不对通知进行手动移除,那么指针指向被回收的内存区域就会成为野指针,这时再发送通知,便会造成程序崩溃。从 iOS9 开始通知中心会对观察者进行 weak 弱引用,这时即使不对通知进行手动移除,指针也会在观察者被回收后自动置空,这时再发送通知,向空指针发送消息是不会有问题的。建议最好加上移除通知的操作:

(void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self.observer name:@"name" object:nil]; }
  • 情况六:WKWebView 造成的内存泄漏
    总的来说,WKWebView 不管是性能还是功能,都要比 UIWebView 强大很多,本身也不存在内存泄漏问题,但是,如果开发者使用不当,还是会造成内存泄漏。请看下面这段代码:

@property (nonatomic, strong) WKWebView *wkWebView;
 * (void)webviewMemoryLeak { WKWebViewConfiguration *config =[[WKWebViewConfiguration alloc] init];
  config.userContentController = [[WKUserContentController alloc] init]; 
[config.userContentController addScriptMessageHandler:self name:@"WKWebViewHandler"]; 
 _wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config]; 
 _wkWebView.backgroundColor = [UIColor whiteColor];
 [self.view addSubview:_wkWebView]; 
 NSURLRequest *requset = [NSURLRequest requestWithURL:[NSURL URLWithString:@"[https://www.baidu.com](https://www.baidu.com/)"]]; 
[_wkWebView loadRequest:requset]; } 这样看起来没有问题,但是其实 “addScriptMessageHandler” 这个操作,导致了 wkWebView 对 self 进行了强引用,然后 “addSubview”这个操作,也让 self 对 wkWebView 进行了强引用,这就造成了循环引用。解决方法就是在合适的机会里对 “MessageHandler” 进行移除操作:  
* (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; 
[_wkWebView.configuration.userContentController removeScriptMessageHandlerForName:@"WKWebViewHandler"]; }
  • 内存泄漏的查询

  • 第一种查询方式Analyze 静态分析 (command + shift + b)也就是编译,主要分析以下四种问题:

    • 逻辑错误:访问空指针或未初始化的变量等;

    • 内存管理错误:如内存泄漏等;

    • 声明错误:从未使用过的变量;

    • Api调用错误:未包含使用的库和框架。

  • 第二种查询方式Instruments中的Leak动态分析内存泄漏,product->profile ->leaks 打开工具主窗口

  • 第三种Facebook早已开源了一款检测内存问题的三方库FBRetainCycleDetector


老易
5楼 · 2021-01-28 10:45
Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的可能是24M,当我们的内存占用超过了一定的程度后,就会出现OutOfMemory的错误。
下面说明几点可能导致内存泄露的原因,供大家参考。
1.对象内存过大 
保存了多个好用内存过大的对象,造成内存超出限制。
2.资源释放
程序代码的问题,长期保持某些资源,如Context,Cursor,IO流的引用,资源得不到释放造成内存泄露。
3.static关键字的使用
static 是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例,就可能会造成内存的泄露。
针对static的解决方案:
应该尽量避免static成员变量引用资源耗费过多的实例,比如Context.
Context尽量使用ApplicationContext的生命周期比较长,引用它不会出现内存泄露。
使用WeakReference代替强引用。比如可以使用WeakReference mContext;
4.线程导致内存溢出线程产生内存泄露的主要原因在于线程生命周期的不可控。如当我们切换横竖屏的时候,一般会重新创建Activity,老的Activity应该被销毁。但是此时我们在子线程中正在进行耗时的操作,老的Activity不会被销毁,这个时候就会出现内存泄露。
解决方案:
将线程的内部类,改为静态内部类。
在线程内部采用弱引用保存Context引用。


一朵大红花啊
6楼 · 2021-01-28 13:52

针对这种线程的内存泄漏问题的解决方案:1)将线程的内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。2)在线程内部采用弱引用保存Context的引用。

1.集合类泄漏

集合如果只有添加元素的方法,没有相应的删除机制,就会到之后内存被占用。

如果这个集合是全局的变量,没有相应的删除机制,就会导致集合占用的内存只增加不减。

2.单例造成的内存泄漏

单列的静态特性的生命周期和应用的生命周期一样长,如果使用不恰当,就会造成内存泄露。

3.匿名内部类/非静态内部类和异步线程

非静态内部类创建静态实例造成的内存泄漏

4.匿名内部类

Android 开发经常会继承继承实现activity、fragment、view,如果使用了匿名类,就异步线程持有,没有任何措施就会内存泄漏。

5.Handler 造成的内存泄漏

handler、message和messageQueue都是相互关联在一起的,万一handler发送的message没有被处理,

message和发送的handler对象被线程messagequeue一直持有。handler属于tls变量,生命周期和view,activity是不一致的,

导致无法正常释放。

6.尽量避免使用 static 成员变量

如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,

占内存较大的后台进程将优先回收,yi'wei如果此app做过进程互保保活,那会造成app在后台频繁重启。

当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。

7.避免 override finalize()

8.资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,

应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。



相关问题推荐

  • 回答 97
    已采纳

    Js给初学者的印象总是那么的杂而乱,相信很多初学者都在找轻松学习Js的途径。在这里给大家总结一些学习Js的经验,希望能给后来的学习者探索出一条轻松学习Js之路。Js给人那种感觉的原因多半是因为它如下的特点:A:本身知识很抽象、晦涩难懂,如:闭包、内置...

  • 回答 4

    看图:

  • 回答 18

    基本类型( 6种 ):Number ( 数值 ) String ( 字符串 )Boolean ( 布尔 ) Undefined ( 未定义 )Null ( 空 )ES6 - Symbol  ( 唯一 )

  • 回答 19

    JavaScript 使网页增加互动性,使有规律地重复的HTML文段简化,减少下载时间。JavaScript 能及时响应用户的操作,对提交表单做即时的检查,无需浪费时间交由 CGI 验证。JavaScript 的特点是无穷无尽的,只要你有创意。...

  • 回答 18

    timeoutId: 定时器IDfunc: 延迟后执行的函数code: 延迟后执行的代码字符串,不推荐使用原理类似eval()delay: 延迟的时间(单位:毫秒),默认值为0param1,param2: 向延迟函数传递而外的参数,IE9以上支持setInterval: 以固定的时间间隔重复调用一个函...

  • 回答 15

    Number类型String类型Boolean类型Undefined类型Null类型

  • 回答 14

    空格在ASCII中的值为32,空字符为0,可本人觉得不就是都是为空的吗,没有什么区别?char mychar1,mychar2;mychar1=' ';mychar2='\0';printf(mychar1=%d,mychar2=%d,mychar1,mychar2);//其中mychar1=32;mychar2=0;...

  • 回答 16

    1.变量名可以有数字0~9、大小写字母、下划线、美元符$组成。2.变量名不能以数字开头。 当我们以数字为开头时,代码就会出现橙色下划线,代表命名不...3.变量名不允许使用中文。 不能允许使用中文这个就不用多说了吧,不管你在哪找代码来看,代码中...4.区分大小写...

  • 回答 8

    向一个对象数组里面添加新的属性var arry= [{a:11,b:22,c:33,d:44},{a:11,b:0,c:0,d:44},{a:11,b:22,c:99,d:99}];var arry2=[];arry.map(((item, index)=> {arry2.push(Object.assign({},item,{mess1:item.c,mess2:item.d}))}))cons...

  • 回答 2

    我觉得getTopWindow() 应该是他自己写的函数 mask  应该是getTopWindow()函数中 return 出的一个什么玩意show()  jQuery的显示

  • 回答 16

    看上图

  • 回答 9

    如图所示

  • 回答 12

    1、原型对象也是普通的对象,是对象一个自带隐式的 __proto__ 属性,原型也有可能有自己的原型,如果一个原型对象的原型不为 null 的话,我们就称之为原型链 2、 原型链是由一些用来继承和共享属性的对象组成的(有限的)对象链...

  • js选项卡的实现原理2021-06-15 21:48
    回答 6

    如图所示,最简单的选项卡思路:选项卡就是点击按钮切换到相应内容,其实就是点击按钮把内容通过display(block none)来实现切换的。1、首先获取元素。2、for循环历遍按钮元素添加onclick 或者 onmousemove事件。3、因为点击当前按钮时会以高亮状态显示,所以...

  • 回答 4

    1、js截取两个字符串之间的内容:123var str = aaabbbcccdddeeefff; str = str.match(/aaa(\S*)fff/)[1]; alert(str);//结果bbbcccdddeee2、js截取某个字符串前面的内容:123var str = aaabbbcccdddeeefff; tr = str.match(/(\S*)fff/)[1];......

  • 回答 4

    如果是ajax 就直接获取如果是传到一个页面 就再get再在js中使用 就可以获取了。 可以在js中获取一个变量 但是不能写入一段java代码.

没有解决我的问题,去提问