使用Android studio和MAT分析Android内存泄漏

1.内存泄漏是什么

内存泄漏就是一些已经不使用的对象还存在于内存之中且垃圾回收机制无法回收它们,导致它们常驻内存,会使内存消耗越来越大,最终导致程序性能变差。
其中在Android虚拟机中采用的是根节点搜索算法枚举根节点判断是否是垃圾,虚拟机会从GC Roots开始遍历,如果一个节点找不到一条到达GC Roots的路线,也就是没和GC Roots 相连,那么就证明该引用无效,可以被回收,内存泄漏就是存在一些不好的调用导致一些无用对象和GC Roots相连,无法被回收。既然知道了什么是内存泄漏,自然就知道如何去避免了,就是我们在写代码的时候尽量注意产生对无用对象长时间的引用

2.案例分析

内存泄漏的示例代码如下:

// 导致内存泄漏的代码
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
textView = (TextView) findViewById(R.id.text);
final Handler handler = new Handler();
handler.post(new Runnable() {
 @Override
 public void run() {
   textView.setText(String.valueOf(timer++));
   handler.postDelayed(this, 1000);
  }
 });
}
2.1内存分析工具

Android的内存泄漏分析工具常用有Android Studio和基于eclipse的MAT(Memory Analyzer Tool)。通过两者配合,可以发挥出奇妙的效果。Android Studio能够快速定位内存泄漏的Activity,MAT能根据已知的Activity快速找出内存泄漏的根源。

1.第一步:强制GC,生成Java Heap文件

我们都知道Java有一个非常强大的垃圾回收机制,会帮我回收无引用的对象,这些无引用的对象不在我们内存泄漏分析的范畴,Android Studio有一个Android Monitors帮助我们进行强制GC,获取Java Heap文件。

强制GC:点击Initate GC(1)按钮,建议点击后等待几秒后再次点击,尝试多次,让GC更加充分。然后点击Dump Java Heap(2)按钮,然后等到一段时间,生成有点慢。



生成的Java Heap文件会在新建窗口打开。



2.分析内存泄漏的Activity

点击Analyzer TasksPerform Analysis(1)按钮,然后等待几秒十几秒不等,即可找出内存泄漏的Activity(2)。


那么我们就可以知道内存泄漏的Activity,因为这个例子比较简单,其实在(3)就已经可以看到问题所在,如果比较复杂的问题Android Studio并不够直观,不够MAT方便,如果Android Studio无法解决我们的问题,就建议使用MAT来分析,所以下一步我们就生成标准的hprof文件,通过MAT来找出泄漏的根源。

3.转换成标准的hprof文件

刚才生成的Heap文件不是标准的Java Heap,所以MAT无法打开,我们需要转换成标准的Java Heap文件,这个工具Android Studio就有提供,叫做Captures,右击选中的hprofExport to standard .hprof选择保存的位置,即可生成一个标准的hprof文件。



4.MAT打开hprof文件

MAT的下载地址,使用方式和eclipse一样,这里就不多说了,打开刚才生成的hprof文件。点击(1)按钮打开Histogram。(2)这里是支持正则表达式,我们直接输入Activity名称,点击enter键即可。


搜索到了目标的Activity


右击搜索出来的类名,选择Merge Shortest Paths to GC Rootsexclude all phantom/weak/soft etc. references,来到这一步,就可以看到内存泄漏的原因,我们就需要根据内存泄漏的信息集合我们的代码去分析原因。



6.根据内存泄漏信息和代码分析原因

使用Handler案例分析,给出的信息是Thread和android.os.Message,这个Thread和Message配合通常是在Handler使用,结合代码,所以我猜测是Handler导致内存泄漏问题,查看代码,直接就在函数中定义了一个final的Handler用来定时任务,在Activity的onDestroy后,这个Handler还在不断地工作,导致Activity无法正常回收。

修改代码如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
textView = (TextView) findViewById(R.id.text);
handler.post(new Runnable() {
 @Override
 public void run() {
  textView.setText(String.valueOf(timer++));
  if (handler != null) {
   handler.postDelayed(this, 1000);
  }
 }
});
}
private Handler handler = new Handler();
@Override
protected void onDestroy() {
 super.onDestroy();
 // 避免Handler导致内存泄漏
 handler.removeCallbacksAndMessages(null);
 handler = null;
}

3.常见的内存泄漏分析

内存泄漏的主要问题可以分为以下几种类型:

    a.静态变量引起的内存泄漏
    b.非静态内部类引起的内存泄漏
    c.资源未关闭引起的内存泄漏

3.1静态变量引起的内存泄漏

在java中静态变量的生命周期是在类加载时开始,类卸载时结束。换句话说,在android中其生命周期是在进程启动时开始,进程死亡时结束。所以在程序的运行期间,如果进程没有被杀死,静态变量就会一直存在,不会被回收掉。如果静态变量强引用了某个Activity中变量,那么这个Activity就同样也不会被释放,即便是该Activity执行了onDestroy(不要将执行onDestroy和被回收划等号)。这类问题的解决方案为:1.寻找与该静态变量生命周期差不多的替代对象。2.若找不到,将强引用方式改成弱引用。比较典型的例子如下:

单例引起的Context内存泄漏:

public class IMManager {
  private Context context;
  private static IMManager mInstance;
 
  public static IMManager getInstance(Context context) {
    if (mInstance == null) {
      synchronized (IMManager.class) {
        if (mInstance == null)
          mInstance = new IMManager(context);
      }
    }
    return mInstance;
  }
 
  private IMManager(Context context) {
    this.context = context;
  }
 
}

当调用getInstance时,如果传入的context是Activity的context。只要这个单例没有被释放,这个Activity也不会被释放。

解决方案:传入Application的context,因为Application的context的生命周期比Activity长,可以理解为Application的context与单例的生命周期一样长,传入它是最合适的。修改如下:

public class IMManager {
  private Context context;
  private static IMManager mInstance;
 
  public static IMManager getInstance(Context context) {
    if (mInstance == null) {
      synchronized (IMManager.class) {
        if (mInstance == null)
          //将传入的context转换成Application的context
          mInstance = new IMManager(context.getApplicationContext());
      }
    }
    return mInstance;
  }
 
  private IMManager(Context context) {
    this.context = context;
  }
 
}

3.2非静态内部类引起的内存泄漏

在java中,创建一个非静态的内部类实例,就会引用它的外围实例。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。这类问题的解决方案为:1.将内部类变成静态内部类 2.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用。3.在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务。

1.内部线程造成的内存泄漏:

public class LeakAty extends Activity {
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.aty_leak);
    test();
  }
 
  public void test() {
    //匿名内部类会引用其外围实例LeakAty.this,所以会导致内存泄漏
    new Thread(new Runnable() {
 
      @Override
      public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }).start();
  }
  }

解决方案:将非静态匿名内部类修改为静态匿名内部类

public class LeakAty extends Activity {
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.aty_leak);
    test();
  }
  //加上static,变成静态匿名内部类
  public static void test() {
    new Thread(new Runnable() {
 
      @Override
      public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }).start();
  }
}

另外一个例子:

    package com.example.memoryleak;  
      
    import java.util.ArrayList;  
    import java.util.List;  
      
    import android.app.Activity;  
    import android.os.Bundle;  
      
    public class LeakActivity extends Activity {  
        private List<String> list = new ArrayList<String>();  
          
      
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
              
            //模拟Activity一些其他的对象  
            for(int i=0; i<10000;i++){  
                list.add("Memory Leak!");  
            }  
              
            //开启线程  
            new MyThread().start();  
              
        }  
          
          
        public class MyThread extends Thread{  
      
            @Override  
            public void run() {  
                super.run();  
                  
                //模拟耗时操作  
                try {  
                    Thread.sleep(10 * 60 * 1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                  
            }  
              
              
        }  
    }  
我们知道上面的例子代码中我们知道内部类会持有外部类的引用,如果内部类的生命周期过长,会导致外部类内存泄露,那么你会问,我们应该怎么写那不会出现内存泄露的问题呢?既然内部类不行,我们就外部类或者static的内部类,如果我们需要用到外部类里面的一些东西,我们可以将外部类Weak Reference传递进去

修改如下:

    package com.example.memoryleak;  
      
    import java.lang.ref.WeakReference;  
    import java.util.ArrayList;  
    import java.util.List;  
      
    import android.app.Activity;  
    import android.os.Bundle;  
      
    public class LeakActivity extends Activity {  
        private List<String> list = new ArrayList<String>();  
          
      
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
              
            //模拟Activity一些其他的对象  
            for(int i=0; i<10000;i++){  
                list.add("Memory Leak!");  
            }  
              
            //开启线程  
            new MyThread(this).start();  
              
        }  
          
          
        public static class MyThread extends Thread{  
            private WeakReference<LeakActivity> mLeakActivityRef;  
              
            public MyThread(LeakActivity activity){  
                mLeakActivityRef = new WeakReference<LeakActivity>(activity);  
            }  
      
            @Override  
            public void run() {  
                super.run();  
                  
                //模拟耗时操作  
                try {  
                    Thread.sleep(10 * 60 * 1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                  
                //如果需要使用LeakActivity,我们需要添加一个判断  
                LeakActivity activity = mLeakActivityRef.get();  
                if(activity != null){  
                    //do something  
                }  
                  
            }  
              
              
        }  
    }  

2.Handler造成的内存泄漏

泄漏代码如下:

    package com.example.memoryleak;  
      
    import android.app.Activity;  
    import android.os.Bundle;  
    import android.os.Handler;  
    import android.os.Message;  
      
    public class LeakActivity extends Activity {  
      
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
              
            MyHandler handler = new MyHandler();  
            handler.sendMessageDelayed(Message.obtain(), 10 * 60 * 1000);  
        }  
          
          
        public class MyHandler extends Handler{  
      
            @Override  
            public void handleMessage(Message msg) {  
                super.handleMessage(msg);  
            }  
        }  
              
    }  

我们知道使用MyHandler发送消息的时候,Message会被加入到主线程的MessageQueue里面,而每条Message的target会持有MyHandler对象,而MyHandler的this$0又会持有LeakActivity对象, 所以我们在旋转屏幕的时候,由于每条Message被延迟了 10分钟,所以必然会导致LeakActivity泄露,所以我们需要将代码进行修改下:

    package com.example.memoryleak;  
      
    import java.lang.ref.WeakReference;  
      
    import android.app.Activity;  
    import android.os.Bundle;  
    import android.os.Handler;  
    import android.os.Message;  
      
    public class LeakActivity extends Activity {  
        MyHandler handler;  
      
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
              
            handler = new MyHandler(this);  
            handler.sendMessageDelayed(Message.obtain(), 10 * 60 * 1000);  
        }  
          
          
        public static class MyHandler extends Handler{  
            public WeakReference<LeakActivity> mLeakActivityRef;  
              
            public MyHandler(LeakActivity leakActivity) {  
                mLeakActivityRef = new WeakReference<LeakActivity>(leakActivity);  
            }  
      
            @Override  
            public void handleMessage(Message msg) {  
                super.handleMessage(msg);  
                  
                if(mLeakActivityRef.get() != null){  
                    //do something  
                }  
            }  
        }  
      
      
        @Override  
        protected void onDestroy() {  
            handler.removeCallbacksAndMessages(null);  
            super.onDestroy();  
        }  
              
    }  

上面的代码就能保证LeakActivity不会被泄露,注意我们在Activity的onDestory方法中使用了 handler .removeCallbacksAndMessages (null),这样子能保证LeakActivity退出的时候,每条Message的target  MyHandler也会被释放, 所以我们在使用非static的内部类的时候,要注意该内部类的生命周期是否比外部类要长,如果是的话我们可以使用上面的解决方法。


3.3资源未关闭引起的泄漏

1.资源对象没有关闭,比如数据库操作中得Cursor,IO操作的对象

2.调用了registerReceiver注册广播后未调用unregisterReceiver()来取消

3.调用了View.getViewTreeObserver().addOnXXXListener ,而没有调用View.getViewTreeObserver().removeXXXListener

4.Android 3.0以前,没有对不在使用的Bitmap调用recycle(),当然在Android 3.0以后就不需要了




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android内存分析是指通过分析应用程序在运行过程中的内存使用情况,找出内存泄漏、内存溢出等问题,以优化应用程序的性能和稳定性。常用的工具有Android Studio提供的内存分析器和MAT(Memory Analyzer Tool)等。 在进行Android内存分析时,可以采取以下步骤: 1. 监测内存使用使用Android Studio提供的内存监视工具,观察应用程序在运行过程中的内存使用情况,包括堆内存和非堆内存的使用情况。 2. 寻找内存泄漏:通过观察内存使用情况,查找是否有对象没有被正确释放,从而导致内存泄漏。可以使用内存分析器来分析堆快照,查找对象引用关系,找出不再需要的对象。 3. 优化内存占用:观察哪些对象占用了大量内存,并尝试优化其内存占用。例如,可以考虑使用弱引用或软引用来管理对象,减少不必要的缓存等。 4. 避免内存溢出:注意合理管理大数据集合、避免频繁创建大对象、及时释放不需要的资源等,以避免应用程序因为内存溢出而崩溃。 5. 使用内存分析工具:Android Studio提供了内存分析器,可以帮助开发者分析内存使用情况,找出内存泄漏和优化内存占用。MAT是一款Java堆内存分析工具,也可用于Android内存分析。 通过进行Android内存分析,开发者可以及时发现和解决应用程序的内存问题,提升应用程序的性能和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值