Android内存泄漏全面总结

概述

内存泄漏伴随着整个APP的开发过程,一个对象内存泄漏可能你不会在意,但是一个对象反复内存泄漏或者多个对象同时内存泄漏,将导致你的APP内存直线上升,严重的还会导致OOM直接crash,所以我们在开发中应该及时发现内存泄漏并且修复它是非常必要的,今天我们在总结一下各种内存泄漏的场景和修复方案。

什么是内存泄漏?

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

简单的说就是:生命周期较长的对象持有了生命周期较短的对象。

Java的GC基本知识

  • GC基本知识

Java的垃圾回收机制与C语言不一样,C语言需要开发者自己实现垃圾回收,而Java的垃圾回收由JVM自动完成.

堆是Java GC管理的主要区域,GC在对堆进行回收前,首先要确定对象是否已死(不可能再被使用的对象)

在Java中, 可作为GC Roots的对象包括:

  • 方法区: 类静态属性引用的对象;
  • 方法区: 常量引用的对象;
  • 虚拟机栈(本地变量表)中引用的对象.
  • 本地方法栈JNI(Native方法)中引用的对象。

GC在回收一个对象前需要经过两个阶段,

1、判断该对象为垃圾对象;
2、回收该对象释放内存;

判断该对象为垃圾对象算法:

  • 引用计数算法(早起的算法)

    引用计数算法在对象引用一次时自动加1,当引用失效的时候,该对象的引用计数器就会减1,当值变成0的时候,就判断为可以回收的对象。

    但是这个算法有个明显的缺陷:当两个对象相互引用,但是二者都已经没有作用时,理应把它们都回收,但是由于它们相互引用,不符合垃圾回收的条件,所以就导致无法处理掉这一块内存区域

  • 可达性分析算法

    将一系列的GC Root(跟对象)的对象作为起点,从起点向下搜索,搜索走过的路径叫做引用链,当一个对象到GC root没有任何引用链时,则说明该对象为垃圾对象,可以做回收处理;

    常见的GC Root有一下几种:

    • Java虚拟机栈中引用的对象,如在某个方法里面定义的局部变量:

      private void test(){
              Person person = new Person();
              ...
          }
      
    • 静态属性引用的对象:

      private static Person person = new Person();
      
    • 常量引用的对象:

      private static final Person person = new Person();
      
    • 本地方法栈中引用的对象.

常见的内存泄漏场景:

单例模式造成的内存泄漏
public class Test2Activity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LazySingleton lazySingleton = LazySingleton.newInstance(this);
    }
}

LazySingleton类:

public class LazySingleton {

    private static LazySingleton lazySingleton;
    private Context mContext;
    private LazySingleton(Context context) {
        mContext = context;
    }

    public static LazySingleton newInstance(Context context) {
        if (lazySingleton == null) {
            synchronized (LazySingleton.class) {
                if (lazySingleton == null){//双重检查锁定
                    lazySingleton = new LazySingleton(context);
                }
            }
        }
        return lazySingleton;
    }

}

- 泄漏说明:

单例模式LazySingleton是一个静态变量,生命周期和APP的生命周期一样长,当LazySingleton.newInstance(this)中传入的是Activity时,Activity被LazySingleton引用,所以当Activity退出时并不会被销毁,从而产生内存泄漏;

解决方法:

这种情况有两种解决办法:

  • 方法一:
 LazySingleton lazySingleton = LazySingleton.newInstance(this);
改成:
 LazySingleton lazySingleton = LazySingleton.newInstance(getApplicationContext());
  • 方法二:
private LazySingleton(Context context) {
    mContext = context;
}
改成
private LazySingleton(Context context) {
    mContext = context.getApplicationContext();
}

非静态内部类或匿名内部类

非静态内部类或匿名内部类对外部类有一个隐式的强引用

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

泄漏说明:

上面的事例代码中匿名内部类隐式的引用了Activity对象,所以如果在子线程未完成sleep(100000)时退出Activity,Activity不会被销毁,此时就好产生内存泄漏;

Handler的使用造成的内存泄漏

  • Handler的postDelayed
public class Test2Activity extends Activity {

    private TextView textView;
    private Handler mHandler;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                String time = getCurrentTime();
                textView.setText(time);
                mHandler.postDelayed(this,1000);
            }
        },1000);
    }
    
    private String getCurrentTime(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM--dd HH:mm:ss");
        return sdf.format(new Date());
    }
}
  • Handler的sendMessage
public class Test2Activity extends Activity {

    private final static int MESSAGE = 0x01 ;
 
    private final Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.sendMessage( MESSAGE ) ;
                try {
                    Thread.sleep( 100000 );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendMessage( MESSAGE ) ;
            }
        }).start() ;
 
    }
}

泄漏说明:

Handler的postDelayed事例中的匿名内部类Runnable在postDelayed的时候会被发送到Message的callback属性中,而Message被发送到MessageQueue中,MessageQueue中的信息通过Looper一个一个被读出来放到run方法中执行,MessageQueue引用了匿名内部类Runnable对象,而Runnable对象有隐式的引用了Activity,mHandler中每隔1s中执行一下postDelayed,一直循环下去,如果此时退出Activity,循环被不会停止,从而导致内存泄漏;

Handler的sendMessage同理。

解决方法:

在onDestory中移除Message

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mHandler != null){
            mHandler.removeCallbacksAndMessages(null);
        }
    }

AsyncTask造成的内存泄漏

private TextView textView;

new AsyncTask<...> {
    @Override
    protected void onPostExecute(Objecto) {
        textView.setText("memory leak");
    }
}.execute();

泄漏说明:

该实例中存在两处内存泄漏:

1、在onPostExecute方法中持有textView方法,在doInBackground未完成前退出Activity,会导致内存泄漏;

2、AsyncTask是匿名内部类,隐式的持有Activity对象,在doInBackground未完成前退出Activity,会导致内存泄漏;

解决方法:

方法一:在创建AsyncTask的时候创建对象名,在onDestroy时取消当前AsyncTask任务

    AsyncTask asyncTask = new AsyncTask<...> {
        @Override
        protected void onPostExecute(Objecto) {
            textView.setText("memory leak");
        }
    }.execute();
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(asyncTask != null){
           asyncTask.cancel(true);
        }
    }

方法二:使用弱引用封装一个WeakAsyncTask,Activity销毁后,当GC的时候将会销毁该Task任务

public abstract class WeakAsyncTask<Params, Progress, Result,WeakTarget> extends
        AsyncTask<Params,Progress,Result> {
    protected WeakReference<WeakTarget> mTarget;

    public WeakAsyncTask(WeakTarget target) {
        mTarget = new WeakReference<WeakTarget>(target);
    }


    @Override
    protected final void onPreExecute() {
        final WeakTarget target = mTarget.get();
        if (target != null) {
            this.onPreExecute(target);
        }
    }

    @Override
    protected final Result doInBackground(Params... params) {
        final WeakTarget target = mTarget.get();
        if (target != null) {
            return this.doInBackground(target,params);
        } else {
            return null;
        }
    }

    @Override
    protected final void onPostExecute(Result result) {
        final WeakTarget target = mTarget.get();
        if (target != null) {
            this.onPostExecute(target,result);
        }
    }

    protected void onPreExecute(WeakTarget target) {
        // No default action
    }

    protected abstract Result doInBackground(WeakTarget target, Params... params);

    protected void onPostExecute(WeakTarget target, Result result) {
        // No default action
    }
}

在Activity中使用

private class MyTask extends WeakAsyncTask<Void, Void, String,MainActivity> {

    public MyTask(MainActivity target) {
        super(target);
    }

    @Override
    protected String doInBackground(MainActivity target, Void... params) {
        //获取context,执行一些操作
        Context context = target;

        return "Hello Android    !!!!!";
    }

    @Override
    protected void onPostExecute(MainActivity target, String s) {
        //执行操作
    }
}

MyTask使用方式:

new MyTask(this).execute();

资源未关闭造成的内存泄漏:

BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等使用完成后需要及时的关闭或者注销

集合容器中的内存泄露

当我们将一个对象或属性加入到一个集合类中,在不需要使用的时候应该及时从集合中remove掉,不然集合将会越来越大,是静态的集合则集合内部的对象生命周期与APP一样长,造成内存泄漏;

WebView造成的泄露

WebView是非常耗内存,由于WebView在渲染页面的时候会调用Native的方法,所以即使在Activity调用OnDestory方法调用WebView.onDestory();也无法完全销毁WebView,WebView正确注销方式:

public void destroy() {
    if (mWebView != null) {
        mWebView.stopLoading();
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.clearView();
        mWebView.removeAllViews();
        try {
            mWebView.destroy();
        } catch (Throwable ex) {
        }
    }
}

无限循环的属性动画造成的内存泄漏

ObjectAnimator ob = ob.ofFloat(,"rotation",0,360).setDuration(2000);
ob.setRepeatCount(ValueAnimator.INFINITE);
ob.start();

泄漏说明:

若在OnDestory()中没有停止动画,则动画即时不可见,仍会一直执行下去,textView持有Activity的引用,故会导致Activity无法被回收.

解决方法:

在onDestory的时候停止动画

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(ob != null){
           ob.end();
           ob = null;
        }
    }

静态变量持有了Activity

public class Test2Activity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Person person = new Person(this);
    }
}

Person:

import android.content.Context;

public class Person {
    private static Context mContext;
    public Person(Context context){
        mContext = context;
    }
}

泄漏说明:

在Person对象中将Context设置成静态变量,而在Activity中使用使用将Activity传入,由于静态变量不会被垃圾回收器回收,所以Activity一直无法被回收,从而导致内存泄漏;

解决办法:

这种情况有三种解决办法:

  • 方法一:
Person person = new Person(this);
改成:
Person person = new Person(getApplicationContext());
  • 方法二:
private static Context mContext;
改成:
private Context mContext;
  • 方法三:
public Person(Context context){
    mContext = context;
}
改成
public Person(Context context){
    mContext = context.getApplicationContext();
}

总结

内存泄漏是造成应用程序OOM的主要原因之一,然而Android中内存泄漏的场景非常的多,只有熟悉各种内存泄漏的场景,才能在开发过程中有效的避免内存泄漏,当然我们可以通过LeakCanary这个工具在开发过程中及时的发现内存泄漏,对LeakCanary有兴趣的可以参考:

LeakCanary使用详细教程(附Demo)
LeakCanary原理分析

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值