1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > Android内存优化(二):一分钟发现内存泄漏

Android内存优化(二):一分钟发现内存泄漏

时间:2021-11-08 17:47:01

相关推荐

Android内存优化(二):一分钟发现内存泄漏

在上一篇文章Android内存优化(一):Java内存区域中已经大体上介绍了Java中的内存分布情况,这一篇主要讲一下内存泄漏的产生原因、内存泄漏的危害、内存泄漏一键分析与定位、以及代码中常见的内存泄漏。

#1内存泄漏的产生原因 前方高能,18岁以下请避让!!!惊天大咪咪:内存泄漏产生的原因是对象占着茅坑不拉屎!!!有必要讲一下Android中的垃圾收集是怎么进行的,Android中使用标记-清除(Mark-Sweep)算法进行垃圾回收(garbage collection,简称GC),就是按照正常套路来说,在坑位(内存)不够的情况下,垃圾收集器会遍历全部对象,看哪些对象是可以被回收掉腾出内存的,这个过程称为Mark(标记),Mark的时候要求除了垃圾收集线程之外,其它的线程都停止,这种吊炸天的现象在垃圾收集算法中称为Stop The World,世界围着他转,这就造成了我们的程序会卡顿,但是一般情况下这个时间就几十毫秒,我根本就感受不到好吗。Mark完之后,就是释放内存空间啦,这个过程称为Sweep(清除)。

这一切看起来很美好,但是就是有内存泄漏发生,所以得提一下,不是所有的对象都是特仑苏,阿呸,不是所有的对象都能被回收的,比如下面的傲娇贱货。

垃圾回收的原则:被全局变量(static)、栈变量和寄存器等直接引用和间接引用的对象不能被回收。

所以说,对象即使已经使用完,但却一直被其它对象引用,就会导致这个对象无法被回收,造成内存的浪费,让别的对象无屎可拉。对象无法被GC回收就是造成内存泄露的原因!

#2内存泄漏的可能会造成的创伤 如果不是利用工具去找的话,一般情况下内存泄漏是比较难发现的,因为Java中不会报内存泄漏这种异常,所以在轻微的内存泄漏表面上看是跟正常情况下没有区别的。

2.1 内存泄漏跟内存溢出(OOM)的区别就是:**量变和质变。**一个两个内存泄漏表面看起来没毛病,但是量变可以导致质变,内存泄漏多了会炸的,就是报OOM异常,应用直接崩溃,连解释的机会都没有。2.2 堆得内存大小是确定的,出现内存泄漏后可用的内存会减小,这又会造成垃圾回收的频率加剧,上面提到过,垃圾回收的Mark阶段会有一种吊炸天的现象,就是Stop The World,除了垃圾回收线程之外的线程会停止,频繁的垃圾回收卡顿明显的感受到。2.3 应用后台运行的时候,内存占用大,进程被系统杀死的概率就会大咯。

#3内存泄漏的发现 内存泄漏的分析的话,必须使用工具才行,庆幸的是,各路大神已经给我们提供了很多强大的内存分析工具,我这里只会讲最方便的。这里提供几个套餐供选择 ####3.1 套餐一:Studio自带Heap Viewer 想不想知道你的应用到底有没有内存泄漏呢?说真,就一分钟的事。

3.1.1打开Studio,连上你的应用,然后Android Monitor (1)->Monitors(2)->Memory,上面有四个图标,暂停图标是开启内存使用状态追踪的开关,默认是开启的,小车图标就是手动GC(3),向下箭头图标(4)是查看堆的分配情况,最后的图标allocation tracker用来跟踪内存分配情况。

3.1.2我讲一下我的使用方式,在应用中操作,从activity1跳转到activity2,然后跳回到activity1界面,这样是为了分析activity2是否会产生内存泄漏。接下来就是真刀真枪的干了。

3.1.3点击小车图标(3),手动GC进行垃圾回收,这样才能更准确的判断activity2是否有内存泄漏发生,最后点击向下箭头图标(4),Studio会自动生成hprof文件并自动展示在Studio界面中。

3.1.4这个就是内存的分析文件了,点击Analyzer Tasks(5),这是让Studio帮我们自动分析是否出现内存泄漏。

3.1.5勾上Detect Leaked Activities(6),最后运行(7)就出现分析结果了

3.1.6看到没,activity2出现内存泄漏了(8),左下角是引用树(9),通过引用树就可以定位到内存泄漏的具体信息了。

####3.2套餐二:Heap Viewer + MAT 是啊,发现有内存泄漏了,然而还有其它的选择,这里就必须使用到其它的工具进行辅助了。

MAT(Memory Analyzer)内存分析工具,这个工具的使用我只简单讲一下,因为我一般不用,不要问为什么,因为用起来比较麻烦一些。3.2.1MAT下载,进入下载的官网,我电脑是64位的,所以选择Windows(x86_64),整个下载安装流程跟一般软件没啥区别,进入新页面然后点击DOWNLOAD

点击click here就可以下载使用了

3.2.2 hprof文件导入,这个文件的获取流程跟内存泄漏的发现流程基本一样,按上面说的通过Studio的Heap工具获取的,但是文件导入前需要进行一下转换,因为MAT工具不能直接使用,转换也 不麻烦,Studio已经帮你简化这个过程,一键导出转换文件,请看过来

3.2.3 用MAT打开hprof的转换文件,其中Histogram和Dominator Tree比较常用,分析内存泄漏特别需要用到Histogram的两份文件对比分析,就是获取两份内存泄漏前后的hprof转换文件

3.2.3 标题栏Window->Navigator History,打开 Navigator History面板,然后点击打开Histogram,![](http://upload-images.jianshu.io/upload_images/4821599-205d2c4bd313d070.png?imageMogr2/auto-orient/strip%7![Uploading 内存8_883765.png . . .] CimageView2/2/w/1240)3.2.4 右键histogram,将两份分析文件的 Histogram结果都添加到 Compare Basket中,点击右上角的!图标就会生成对比文件

3.2.5 这就是最后生成的对比文件,你还可以自己选择对比的方式,红圈里面提供不同的对比方式,这样就可以很直观的看出差异,因为我对比的是同一份文件,所以对象间木有差异。

####3.3套餐三:Leakcanary square的开源内存泄漏分析框架,好用得不得了,配置很简单

3.3.1建议在app的build.gradle文件下添加下面的依赖

dependencies {debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'}```- 3.3.2在你的```Application```中的```onCreate()```方法中进行初始化复制代码

public class ExampleApplication extends Application {

@Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for heap analysis. // You should not init your app in this process. return; } LeakCanary.install(this); // Normal app init code... } }

- 3.3.3然后,就没有然后了,编译完后运行你的项目,会在项目安装成功后出现附加的组件,里面会展示具体的内存泄漏路径。![](http://upload-images.jianshu.io/upload_images/4821599-68cfb470f5cf3bb0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)- 3.3.4通过这个泄漏路径,就对应进行内存泄漏的原因进行分析了,你也可以通过输出的日志进行内存泄漏的定位。![](http://upload-images.jianshu.io/upload_images/4821599-cd2b7b28aed9223e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)>注:到这里3个套餐已经讲完了,关于MAT这个套餐我只是讲一下基本的使用,其实已经够用了,怎么说呢,用起来比较麻烦,所以我自己本身也很少用,我就按自己的使用对比一下三者。套餐三>套餐一>套餐二1.套餐三使用最方便,一劳永逸,解析hprof的速度有点慢,但是因为后台自动解析,所以基本上没多大关系;2.套餐一使用最快,切换一下页面分分钟就知道有没有内存泄漏,但是需要你每一次都要手动操作;3.套餐三最麻烦,耗时耗力,但是自动分析工具并不能保证找出所有的内存泄漏,这个时候就需要通过MAT辅助分析了。#4代码里头内存泄漏的常见原因代码中内存泄漏大多数产生的原因是不遵循activity的生命周期。- 4.1单例模式(静态activity):在你的Activity中定义了一个 static 变量引用了activity,因为static变量的生命周期和app一样长,就算activity被销毁,activity对象还是会被static变量持有,一直到app被销毁,这也是单例模式最容易造成泄漏的原因,如果静态的单例对象持有activity对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。解决办法是使用Application的Context代替activity的context;复制代码

/**

单例模式 */ publicclassSingletonClass{ privatestaticSingletonClassinstance; private Context context; publicstaticSingletonClassgetInstance(Context context){ synchronized(SingletonClass.class){ if(instance==null){ instance=newSingletonClass(Context context); } } returninstance; } privateSingletonClass(Context context){ this.context = context; //传入activity的context就会造成内存泄露咯 } }

- 4.2静态View:当一个view 被加入到界面中时,它就会持有 context 的强引用,也就是我们的 activity。如果我们通过一个static成员变量引用了这个 view,相当于直接引用了 activity,然后就泄漏了;复制代码

private static View view; view = findViewById(R.id.sv_button);

- 4.3非静态内部类:我们都知道,内部类能够引用外部类的成员,这正是内部类的好处所在,但是恰恰是这个优势会导致activity内存泄漏,因为非静态内部类默认持有外部类的引用。如果我们创建了一个内部类的对象,并且通过静态变量持有这个对象,就会导致内存泄漏;复制代码

private static InnerClass inner = new InnerClass();class InnerClass {}复制代码

- 4.4匿名内部类:匿名类同样会持有定义它们的对象的引用,如果在 activity 内定义了一个匿名的 AsyncTask 对象,就有可能发生内存泄漏了。因为在activity被销毁之后AsyncTask可能仍然在运行,这样只能等到AsyncTask执行结束才能回收activity;复制代码

new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { while(true); } }.execute();

- 4.5Handler+Runnable:定义一个匿名的 Runnable 对象并将其提交到 Handler 上也可能导致 activity 泄漏。Runnable对象引用了定义它的 activity 对象,而它会被提交到 Handler 的 MessageQueue 中,如果它在 activity 销毁时还没有被处理,那就会导致内存泄漏了。复制代码

new Handler() { @Override public void handleMessage(Message message) { super.handleMessage(message); } }.postDelayed(new Runnable() { @Override public void run() { while(true); } }, 1000);

- 4.6Thread:原因类似4.5,尽管是在单独的线程执行任务,但是线程还是会默认持有外部对象,任务没有执行完成就不会释放持有的引用;复制代码

new Thread() { @Override public void run() { while(true); } }.start();

- 4.7资源未关闭:如果使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。- 4.8集合容器:在我们做缓存的时候会用一些数据结构来存储一些数据,当我们不需要它时要及时清理,不然就会像滚雪球一样会越来越大,想不泄露都难。> 可以了,造成内存泄露还有很多原因,这就靠慢慢跳坑了,生活太艰难。再话痨一下,“千丈之堤,以蝼蚁之穴溃;百尺之室,以突隙之烟焚。”,所以我推荐套餐三Leakcanary,让你的整个开发过程伴随着内存泄露的监控。复制代码

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。