您的位置:首页 >  新闻中心 > 云通讯公告
  云通讯公告
 

一文搞懂为啥你的应用内存泄漏?

来源:原创    时间:2017-10-20    浏览:0 次

前语

最近在项目中偶然会发现内存走漏现象。一开端仍是一脸懵逼的查来查去:这怎样就走漏了?这样居然没走漏?一向没有个明晰地思路。这几天闲下来,计划仔细收拾学习一下。我在这儿从一个“怎样主动形成内存走漏”的视点来学习,然后了解一下不同办法检测的成果怎样,这样今后再遇到相关问题时就能够很快的处理了。

java gc

首要要有一个大前提,也就是java gc。在大部分虚拟机(包含Android的ART)中,Java都采用了“可达性剖析”算法来进行内存收回,原理是:会有几个引证作为root节点,关于恣意目标来说,如果从root层层遍历,如果找不到关于他的引证链,那么这个目标就被标记为无用,就会在gc时被毁掉。
为何走漏?

内存走漏,即部分目标尽管现已不再运用,可是由于有root持有引证,所以并没有被毁掉,所占用的内存一向没有被开释。一次两次发作影响不大。如果频频发作,那么可用内存会逐渐缺乏,终究在某一次恳求内存时发现内存缺乏而发作oom。这儿要清晰一个概念,只要强引证会发作内存走漏,而weak等引证由于其特别机制,所以影响不大。

走漏影响比较大的就是一些大目标,常见的比方某些资源,bitmap,以及activity。
怎样发作走漏

首要让我们从另一个视点来看,怎样主动发作内存走漏呢?当然是想办法给他一个一向存在的强引证了。
static
static这个关键字使一个变量变为只和这个类相关的类变量,和实例无关。他的生命周期是很长的,贯穿于app的启动到封闭。因而只要用一个static引证一个大目标,就能够走漏了!举个比方:

static Activity activity;
这是最简略粗犷的持有一个activity的引证,这样这个activity退出之后目标并没有被毁掉。

static View view;
一个View初始化时会用到context,我们在自定义View,重写结构办法时就知道这个了。因而如果一个View也像这样被持有,那个context也不会被开释。

innerClass
内部类有个特性,是他会持有一个外部类的引证。如果内部类的实例一向存活,那么外部类activity的实例也就一向在。比方持有一个static的内部类引证:

或许曾经我们用asynctask时喜爱搞一个匿名内部类履行异步使命,那当我们activity退出后这个异步使命还在履行的话,就会走漏了。


还有自己开个匿名线程:


还有在运用handler时,如果用了匿名handler,那么这个handler会带着activity的引证藏到音讯行列中。音讯没有被处理,就会形成内存走漏。相似的,还有timertask等。

register
我们平常会用到许多第三方库,比方ButterKnife EventBus RxJava等等,有的时分要获取体系效劳,getSystemService。在运用的时分,都有一个先registerd或许bind的操作,并且在创立的时分会把activity的引证传过去。如果在activity完毕时没有unregister或许unbind,就会形成内存走漏。

怎样检测走漏

最简略的办法天然就是运用leakcanary了。只要给自己的项目加上这个东西,在发作走漏的时分很快就会有提示。

除此之外,android studio的刀耕火种的方法也不错,在这儿我拿一个比方来演示一下我是怎样用的。

准备工作

首要,我写了两个activity,一个MainActivity,一个MemoryLeakActivity,逻辑是:MainActivity中有个按钮,点击会调到MemoryLeakActivity,在这个activity中会成心发作内存走漏,代码如下:


在开端之前,再了解一下这个



这个Monitors能够调查当时选中app的运转情况,现在只需求重视我标了123的当地。

首要这个Memory就是当时app的内存运用情况:

发作一个当时java堆的.hprof文件,这个文件反映了当时时间java堆中内存概况,记住这个玩意有大用!

手动进行一次gc

这一块很重要,首要他有两个部分,蓝色和灰色。蓝色部分是当时内存运用巨细,灰色部分是这个app被约束的最大内存巨细。当蓝色部分越来越大,最终和灰色部分一样时,阐明我们内存运用许多了行将内存缺乏,此刻会进行一次gc一起将回灰色部分即约束的巨细进步。

肉眼调查
好了,介绍完这个东西,我们开端着手实践。首要翻开app,点击按钮跳到会发作走漏的activity上,再按回来键,然后再次按下按钮……这样重复操作:


与此一起,调查monitors的memory窗口,会发现蓝色部分在每一次敞开新activity时会增加一部分,这很正常。可是在回来时,分明activity被“退出”了,可是蓝色部分仍是没有改变。重复几回之后,蓝色部分一向在增加。也就是说当时内存越用越多,能够揣度现已发作内存走漏啦~

主动剖析

接下因由android studio来剖析一下。在重复几回上面的操作之后,回来MainActivity,然后点击dump java heap按钮,然后等一会儿,android studio在为我们dump此刻的horof文件。在成功后,会主动翻开:



如图在这个界面中,我们看最右面有一个栏叫 Analyzer Tasks,翻开它,会发现有两个选项。我们是来看activity的内存走漏的,那就把那个查重复字符串的√去掉。然后点右边那个绿色小三角,会发现下面Analysis Results栏里边展现出了当时走漏的Activity引证:

点击第一个item,最下方Reference Tree栏中便展现出了详细的引证:



一般来说,第一个就是我们发作走漏的当地。在图中,this$0的意思是隐式的引证。也就是说,我们的activity是由于一个内部类而发作了内存走漏。

再点击方才results中第二个item,看一下下方的reference tree:



能够看到显式的有一个leakCntextRef引证,这阐明我们有一个名为leakCntextRef的引证持有了activity。回过头看看我们的代码,公然,验证的没错。

拓宽
android studio的剖析还算比较简略并且内容较少,我们能够把这个hprof导出,然后用mat来剖析。

怎样处理走漏

已然发作了走漏,那就要处理它,防止问题呈现。那么怎样处理呢?很简略,走漏是由于持有了activity引证导致无法被毁掉,那么只要两个挑选:及时撤销引证,或许让这个引证多待一会,可是该gc的时分就毁掉。

依据这个思路:
我们在代码中能不必static变量持有contxt就不必,非要用就用weak引证。

关于内部类,尽量用静态内部类,这样就不会持有外部类引证。如果需求外部类引证做一些事,就手动赋给一个weak引证。

关于匿名内部类,不要图简略便利,真实不可就乖乖的写成外部类。

异步操作,尽量用能够便利办理的,比方rxJava,而不是用老古董AsyncTask了。非要用也最好加一个停止条件,在退出Activity时就该完毕了。

在用rx时,能够在subscribe()的时分获取到Subscripeion,在不必的时分手动unSubscribe(),或许直接bind()到Activity的生命周期上,比方运用RxActivity办理。

在运用handler时,记住在activity的onDestroy()中加上remove()

在获取到某些资源时,运用完记住开释

在用到一些大目标比方Bitmap啊什么的,要记住收回

最终,在运用各种第三方库或许体系效劳的时分还要记住有注册或绑定就要有免除注册、解绑定。