Android 内存泄漏---新能优化专题(MAT的使用)

来源:互联网 发布:linux 查看隐藏文件夹 编辑:程序博客网 时间:2024/06/08 15:01

1.问题:内存泄漏是什么?

 内存溢出就不是内存泄漏,内存泄漏过多积累下来就会造成内存溢出。 内存不在G掌控之内了。

GC垃圾回收机制漏掉的垃圾对象–无法回收。(垃圾对象持有的内存一直存在累加)
(1)什么是垃圾回收机制?

      吃完饭餐具不用管,服务员自动回收。      服务员就是GC,什么时候去回收它呢?      某个对象不再有任何的引用的时候才会进行回收。(不能往上追溯到GC Root的对象)      例如在C中持有了D的引用:D d=new D();,这时D能回收么?回收不了可以追溯到GC节点。      例如在F中持有了G的引用:但是追溯不到GC ROOT,那么这个就可以被回收。

这里写图片描述

 可以作为GC Root 的引用的点事:这些都会追溯到GC Root引用点。    JavaStack中的引用的对象。(堆栈中)    方法区中静态引用指向的对象。    方法区中静态常量引用指向的对象。    Native方法中JNI引用的对象。    Thread--活着的线程()

JVM内存模型
这里写图片描述

垃圾对象:1.GC可以直接回收的:2.GC会受不了,但是我们程序员又忘记回收的。

3.确定是否存在内存泄漏—–确定泄漏的大致范围。

   首先我们从容易内存泄漏又很危险的Activity,View等系统重量级的组件查找。   首先我们用AndroidStudIo自带的Android Monitors来查看我们内存情况(相信大家都用过吧):如下图所示:

这里写图片描述

这里是我的一个小小项目用来测试用的:首先我们先运行这个小项目由于这个Demon自己写的所以我在MainPinlun这个Activity里面故   意写了一个内存泄漏的案例代码:

这里写图片描述

 8.63MB 我们同样的再次访问,如果这个Activity有内存泄漏那么内存占用会增大。我们只需要点击app再次启动然后同样步骤退出。

这里写图片描述

 8.91MB同样可以多来几次。为了更加清楚了解详情。我们可以点击Memory Usage来查看。如下图操作之后:第一次和第二次数据对比:1.首先我们运行As:2.点击进入评论Activity里面3.返回键退出应用。4.如下操作:

这里写图片描述

5.直接点击应用打开APP到Activity里面。

这里写图片描述

前后对比两个所占内存逐渐增加。我们可以前后2个的Memory Usage里面对比看:

这里写图片描述

这里写图片描述

这里写图片描述

 第一次进入退出之后有一个Activity被引用没有被回收。Activities: 1第二次进入退出之后有2个Activity被引用没有被回收。Activities:  2这里我们可以分析出这个Activity可能里面导致内存泄漏。上面我们只能大概的推测到这个Activity存在内存泄漏。但是具体是那部分代码我们依然不能够知晓。这时候我们用到Android Monitor的快照功能:

这里写图片描述

如图所示:

这里写代码片

  这里我们可以点击每个Activity类然后查看每个实例(instance),以及最下面的引用树。  左边出现的是当前进程中的所有类,右边是每个类里面的实例。最下面是引用树。

这里写图片描述

 我们可以通过上图来分析,int数组在内存中的实例被引用树中的TypedArray所引用,而Resources引用了TypeArray一次类推,最后WeakReference知道这是弱引用,这种我们不需要关心,因为是弱引用,随时可以被系统回收。那我们如何找内存泄漏的类呢?这么多的类。我们可以通过Package Tree View搜索我们项目包名来缩小我们查询的范围。上面我们可以将所想要测验的Activity都运行之后点击,然后点击Android Monitor里面的快照,然后进行点击左表项目里面的类,如果右边没有实例说明这个类没有泄露,如果有实例引用,但是弱引用那么也可以排除掉。但是在引用树里面也好多引用,这么多引用,我们很难判断。这时候我们就需要更精细的查找泄漏的位置。
4.更精细地查找泄漏的位置
MAT(MemoryAnalyzer)工具的使用:前后可以进行对比:泄漏前后对比。首先我们先获取一次运行的快照并在androidStudio工具的左边栏的Captures中导图.hprof保存到电脑中。然后我们在运行一次上次运行的点击同样的几个想要测试的Activity然后快照,同样导出.hpro文件。然后安装Memory Analyzer工具,然后分别打开两个文件。我们记住第一次和第二次2次对应的文件。然后我们 分别获取Histogram 然后在第二次快照里面点击与另一个比较堆内存,如下图比较过程:点击

这里写图片描述

获取第一个内存分析:

这里写图片描述

点击

这里写图片描述

获取第二个内存分析:

这里写图片描述

这里可以根据包来查看可以缩小范围:

这里写图片描述

然后我们在第二个中点击:

这里写图片描述

按钮进行两个内存比较分析:得出比较结果分析图表:这是我们输入我们项目的包名:如下图com.exa....

这里写图片描述

我们可以看出MainPinglun这个Activity和MIlstView相比要多与原来的。可以大概知道发生泄漏,一般我们先看重量级的Activty,一般Activty引用导致下面的变量被引用情况,所以我们先分析Activty。我们返回到第二个文件中点击分析按钮输入下面包名com.exa.....找到刚才分析出来的泄露内存的Activity然后右键ListObjects->with  Outging references出现引用对象列表:

这里写图片描述

我们点击这个引用之后展开又有好多引用,这时候很难判断,我们可以进行最后一步操作了。去除弱引用,软引用等不必关心系统会自动处理回收的:

这里写图片描述

得到最终定位:

这里写图片描述

我们可以知道在MainPinglun里面有一个Message持有这个Activity引用。我们找到程序:
private Handler mHandler = new Handler() {    @Override    public void handleMessage(Message msg) {      tv_tops.setText(msg.what+"ff");    }};private void loadHandler() {    Message message = Message.obtain();    message.what = 1001;    mHandler.sendMessageDelayed(message, 1000 *60*10);}
这里我们分析代码:这里我们分析指导mHandle是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,而当这个Acvitity退出时候,消息队列中未处理或者正在处理消息,呢么消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,

我们如何解决呢?

我们如何解决呢?我们都知道静态内部类不会依赖外部类,这样我们可以避免Activity被Handler持有引用,但是如果我们需要在MyHandler里面更新UI控件,那么我们可以对Hander持有的对象使用弱引用,这样在回调时候可以回收Hander持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息, 所以我们在Stop或者Destroy时应该移除消息队列中的消息,这样我们可以安全的进行写代码了。修改之后的代码如下:
MyHandler mHandler = new MyHandler(this);private static class MyHandler extends Handler {    private WeakReference<Context> preference;    public MyHandler(Context context) {        if (context != null) {            preference = new WeakReference<Context>(context);        }    }    @Override    public void handleMessage(Message msg) {        super.handleMessage(msg);        MainPinglun activity= (MainPinglun)          preference.get();        activity.tv_tops.setText(msg.what + "ff");    }}@Overrideprotected void onDestroy() {    super.onDestroy();    mHandler.removeCallbacksAndMessages(null);}

然后获取内存分析图:

这里写图片描述
可以看到此时的Activities以及都被回收。

 下一章主要分析Android中常见的几种内存泄漏以及分析并解决。 该吃饭去了,快8点了
原创粉丝点击