Android内存泄漏分析

本文详细介绍了Android中的内存泄漏问题,包括内存泄漏的概念、常见的内存泄漏场景及其解决方案,并提供了具体的代码示例帮助理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android内存泄漏分析

  • 什么是内存泄漏?
  • 哪些情况会导致内存泄漏?
  • 怎么分析查找内存泄漏?

一.什么是内存泄漏

Android垃圾回收采用的是根搜索算法(GC Root Tracing),从GC roots根节点开始对Heap进行遍历,搜索走过的路径叫做引用链(Reperence Chain),到最后,没有直接或间接被GC roots引用到的对象就是垃圾,需要被GC回收。 而内存泄露就是存在无效的引用,导致本应该被GC回收的对象无法被回收。

GC Root Tracing

可以作为GCroot引用点的有
- 虚拟机栈(JavaStack)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- (本地方法)Native方法中JNI引用的对象

GC的作用区域的Java堆,只能回收堆内存中的对象。而方法区,本地方法区和栈不受GC控制,选这些区域的对象作为GC roots,被GC roots引用的对象不能被回收。

二.什么情况会造成内存泄漏

例子1:

private static Leak mLeak;
@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity.second);
    mLeak = new Leak();
}
class Leak{
    //...
}

mLeak是存储在静态区的静态变量,而Leak是内部类,它持有外部类Activity的引用。当外部Activity需要被销毁时,由于其引用被mLeak持有,系统不会对其进行GC,这样就造成了内存泄漏。
解决办法:可将内部类Leak设置为静态内部类。
static class Leak {
}

例子2:

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

我们把Activity传入SingleInstance.getInstance方法,那么instance就持有了这个Activity引用。当instance没有被释放时,Activity就无法被GC,会一直存在,造成内存泄漏。
解决办法:可将new SingleInstance(context) 改为 new SingleInstance(context.getApplicationContext());这样便和传入的activity无关了

例子3:

Android中异步操作并时经常用Handler来传递结果,我们的代码通常会这么实现:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ... 
    }
  }
}

其实这段代码有可能导致内存泄漏。

  1. 在Android中,当一个应用启动时,系统会自动创建一个供主线程使用的Looper实例。Looper的作用就是有一个一个的处理消息队列中的消息对象。在Android中,所有Android框架的事件(包括生命周期方法的调用以及按钮的点按事件等)都是放在消息中,然后加入到Looper要处理的队列中,由Looper一个一个的处理。主线程中的Looper的生命周期和当前应用一样长。
  2. 当一个Handler在主线程被初始化后,我们发送一条message作为Handler的消息到Looper中,实际上message中已经包含了Handler实例的引用。这样才能调用Handler的handlerMessage方法来完成消息的处理。
  3. 在JAVA中,非静态内部类或者是匿名内部类都会持有外部类的引用,静态的内部类不会持有外部类的引用。在上面的例子中,匿名内部类对象handler持有外部类SampleActivity的引用。
    看下面代码就十分明显了:
public class SampleActivity extends Activity {

    private final Handler mLeakHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
          // ... 
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sample);
        mLeakHandler.postDelay(new Runnable(){
            @Override
            public void run(){
                //...
            }
        }, 60*1000*10);
        finish(); // 结束Activity
    }
}

当Activity.finish之后,被延迟的消息会被等待10分钟,由于消息持有handler实例的引用,而handler又是匿名内部类,其持有外部类Activity的引用,导致Activity无法被回收,activity的很多资源也无法被回收,造成activity内存泄漏。
注意,handler中的new Runnable()也是一个匿名内部类对象,同样持有外部类Activity的引用,导致Activity无法被回收。
解决办法:
要解决这种问题,一般使用静态内部类。因为静态内部类的实例不会持有外部类的引用,就不会造成Activity的内存泄漏。静态内部类中需要引用外部类对象时,可以使用弱引用来处理。同样,Runnable也要设置为静态的成员属性。代码修改如下:

public class SampleActivity extends Activity {

    // Instance of static inner class do not hold the implicit reference
    // of their outer class 
    private static class MyHandler extends Handler{
        private WeakReference<SampleActivity> mActivity;

        public MyHandler(Activity activity){
            this.mActivity = new WeakReference<SampleActivity>(activity);

            @Override
            public void handleMessage(Message msg) {
                SampleActivity activity = mActivity.get();
                //...
            }
        }
    }

    private final MyHandler handler = new MyHandler(this);

    private static final Runnale runnable = new Runnable() {
        @Override
        public void run(){
            //...
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity.sample);
        handler.postDelayed(runnable, 60*1000*10);
    }

}

Android很多内存泄漏都是由于外部类调用了非静态内部类导致的,如果非静态内部类对象的生命周期大于Activity的生命周期,就会造成内存泄漏。推荐使用静态内部类+弱引用的方式来解决此类问题。

内存泄漏的检测

Android Studio运行应用程序,点击Android Monitors
这里写图片描述
在Memory一栏中,点击这里写图片描述来执行一次gc,点击这里写图片描述可进入HPROF Viewer界面,来查看JAVA的heap,如下图
这里写图片描述
点击Analyzer Tasks后系统会自动分析出内存泄漏的Activity,在Reference Tree中查看持有该对象的引用。

参考:
http://wetest.qq.com/lab/view/?id=99
http://blog.youkuaiyun.com/a2012s/article/details/9196771
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值