关于内存泄漏

来源:互联网 发布:mac查看u盘隐藏文件 编辑:程序博客网 时间:2024/05/23 12:02

        • 概念
        • 造成影响
        • 内存是如何分配的
        • 内存泄漏产生原因
        • 内存泄漏的出现场景
          • 1单例造成的内存泄漏
          • 非静态内部类创建其静态实例造成内存泄漏
          • 匿名内部类异步线程造成内存泄漏
          • Handler机制造成内存泄漏
          • 资源未回收导致内存泄漏
        • 使用新版Android Studio检测内存泄露和性能
          • 我们怎么检测内存泄露呢
          • 追踪内存分配
          • 查询方法执行的时间
          • 我们如何判断可能有问题的方法
          • Android内存分析命令

见过这个名词已久,但是仍然不能深刻领悟,不明白怎么检测怎么处理/避免。

在ZQiang94的Andriod-collect-blogs合集中存了几篇相关文章,先读一下,做笔记如下,以后收集到更好的理解再附录在文后。hi你个大头鬼等圈内几人在2015年有过相关分享,通过微博宣传的,有空要找出原文。

概念

长生命周期的对象一直持有短生命周期对象的引用,导致短生命周期对象一直被引用而无法被GC回收。

造成影响

内存泄漏是造成OOM的主要原因之一,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。

内存是如何分配的

参考链接1,参考链接2
Android的虚拟机Dalvik机制模仿JVM,也有垃圾回收机制。

Android虚拟机中把内存分为三部分(这一块划分有点混乱),
栈空间,该空间的分配与回收由系统机制决定,垃圾回收不作用在这块区域;栈中只存放基本类型和对象的引用(不是对象)。

堆空间,里面存储是对象的实例,需要开发者主动创建,垃圾回收主要作用在这部分,回收的一个主要策略是检测堆中的对象在栈空间有无对应的引用(垃圾回收知识请另行谷歌)。JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身。

方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。


内存泄漏产生原因

如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。

内存泄露的真因是:持有对象的强引用,且没有及时释放,进而造成内存单元一直被占用,浪费空间,甚至可能造成内存溢出!

其实在Android中会造成内存泄露的情景无外乎两种:
全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。
* 活在Activity生命周期之外的线程。
没有清空对Activity的强引用。*

检查一下你的项目中是否有以下几种情况:

Static ActivitiesStatic ViewsInner ClassesAnonymous ClassesHandlerThreadsTimerTaskSensor Manager

详解见该文章《Android内存泄漏的八种可能》

内存泄漏的出现场景

(实例参见链接)

1、单例造成的内存泄漏

单例模式的生命周期和应用一样长。单例中包含了一个其他对象的引用(比如context),那么即使这个对象不再使用,依然存在一个单例对象引用它,造成无法回收(如果传入一个Activity Context,Activity退出销毁但仍然被单例所引用)。
因此单例中context引用的正确写法是:

this.context = context.getApplicationContext();
2.非静态内部类创建其静态实例造成内存泄漏

非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
正确的做法为:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例。

3.匿名内部类/异步线程造成内存泄漏

new AsyncTask<,,>{
//doInBackground…
}.execute();

new Thread(new Runnable() {
//run…
}).start();

匿名内部类对当前Activity都有一个隐式引用, 如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。
正确的做法还是使用静态内部类。在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源。

static class DemoRunnable implements Runnable{...}static class DemoAsyncTask extends AsyncTask<Void, Void, Void> {...}new Thread(new DemoRunnable()).start();new DemoAsyncTask(this).execute();
4.Handler机制造成内存泄漏

mHandler作为一个非静态匿名内部类,持有一个外部Activity类的引用,我们知道对于消息机制是Looper不断的轮询从消息队列取出未处理的消息交给handler处理。而对于这个例子,每一个消息又持有一个mHandler的引用,每一个mHandler又持有MainActivity的引用。
如果在Activity退出后,消息队列中还存在未处理完的消息,导致该Activity一直被引用,其内存资源无法被回收,导致了内存泄漏。
正确的做法是使用静态内部类和弱引用的写法写Handler。

private DemoHandler mHandler = new DemoHandler(this);private static class DemoHandler extends Handler {        private WeakReference<Context> reference;        public DemoHandler(Context context) {            reference = new WeakReference<>(context);            //将Activity的引用声明为弱引用,可以被GC回收。(引用不一定为Context,也可以是Context的子类)        }        @Override        public void handleMessage(Message msg) {            MainActivity activity = (MainActivity) reference.get();            if(activity != null){                activity.mTextView.setText("");            }        }    }
5.资源未回收导致内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏(这个可以根据IDE的警告改进一部分)。

截取金玉良言如下(有点啰嗦有空再缩减下):

对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。

尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。

对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:

1)将内部类改为静态内部类2)静态内部类中使用弱引用来引用外部类的成员变量 (在Activity中尽量避免使用生命周期不受控制的非静态类型的内部类,可以使用静态类型的内部类加上弱引用的方式实现。)

Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.

在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。

正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。

保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。

使用新版Android Studio检测内存泄露和性能

(此段纯属抄袭,只是为了减少去查找的时间)原文链接

学会使用AS的Memory,里面有按钮发现、收集、追踪内存泄漏,1.3版本以后的Android Studio 检测内存非常方便, 如果结合上MAT工具,LeakCanary插件,一切就变得so easy了。
一般分析内存泄露, 首先运行程序,打开日志控制台,有一个标签Memory ,我们可以在这个界面分析当前程序使用的内存情况, 一目了然。不要脸的继续偷图如下:
用AndroidStudio查看内存泄漏

图中蓝色区域,就是程序使用的内存, 灰色区域就是空闲内存,

当然,Android内存分配机制是对每个应用程序逐步增加, 比如你程序当前使用30M内存, 系统可能会给你分配40M, 当前就有10M空闲, 如果程序使用了50M了,系统会紧接着给当前程序增加一部分,比如达到了80M, 当前你的空闲内存就是30M了。 当然,系统如果不能再给你分配额外的内存,程序自然就会OOM(内存溢出)了。 每个应用程序最高可以申请的内存和手机密切相关,比如我当前使用的华为Mate7,极限大概是200M,算比较高的了, 一般128M 就是极限了, 甚至有的手机只有可怜的16M或者32M,这样的手机相对于内存溢出的概率非常大了。

我们怎么检测内存泄露呢

内存泄露就是指,本应该回收的内存,还驻留在内存中。一般情况下,高密度的手机,一个页面大概就会消耗20M内存,如果发现退出界面,程序内存迟迟不降低的话,可能就发生了严重的内存泄露。我们可以反复进入该界面,然后点击dump java heap 这个按钮,然后Android Studio就开始干活了,下面的图就是正在dump(收集内存泄漏):
正在dump

dump成功后会自动打开 hprof文件,文件以Snapshot+时间来命名:
内存分析结果

通过Android Studio自带的界面,查看内存泄露还不是很智能,我们可以借助第三方工具,常见的工具就是MAT,下载地址 http://eclipse.org/mat/downloads.php ,这里我们需要下载独立版的MAT. 下图是MAT一开始打开的界面, 这里需要提醒大家的是,MAT并不会准确地告诉我们哪里发生了内存泄漏,而是会提供一大堆的数据和线索,我们需要自己去分析这些数据来去判断到底是不是真的发生了内存泄漏。
MAT主界面
接下来我们需要用MAT打开内存分析的文件, 上文给大家介绍了使用Android Studio生成了 hprof文件, 这个文件在呢, 在Android Studio中的Captrues这个目录中,可以找到:
hprof目录
注意,这个文件不能直接交给MAT, MAT是不识别的, 我们需要右键点击这个文件,选择导出标准hprof文件, 转换成MAT识别的。

然后用MAT打开导出的hprof(File->Open heap dump) MAT会帮我们分析内存泄露的原因:
打开hprof文件
分析内存泄漏的原因


LeakCanary
上面介绍了MAT检测内存泄露, 再给大家介绍LeakCanary。
项目地址:https://github.com/square/leakcanaryLeakCanary

啊啊啊啊,抄袭别人的东西太烦,关于LeakCanary直接看原文链接指导(http://www.jianshu.com/p/216b03c22bb8),github上的代码介绍还没看。。
还有这里 http://droidyue.com/blog/2016/03/28/android-leakcanary/
还有这里 http://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

突然发现liaohuqiu好屌。。


继续看AS的强大功能。

追踪内存分配

如果我们想了解内存分配更详细的情况,可以使用Allocation Traker来查看内存到底被什么占用了。用法很简单:
点一下是追踪, 再点一下是停止追踪, 停止追踪后 .alloc文件会自动打开。当你想查看某个方法的源码时,右键选择的方法,点击Jump to source就可以了。图解见原文。

查询方法执行的时间

如果我们要观测方法执行的时间,就需要来到CPU界面,
点击Start Method Tracking, 一段时间后再点击一次, trace文件被自动打开,图解见原文。

我们如何判断可能有问题的方法?

通过方法的调用次数和独占时间来查看,通常判断方法是:

如果方法调用次数不多,但每次调用却需要花费很长的时间的函数,可能会有问题。
如果自身占用时间不长,但调用却非常频繁的函数也可能会有问题。

Android内存分析命令

暂时没用过,仅附上链接:
http://gityuan.com/2016/01/02/memory-analysis-command/

0 0
原创粉丝点击