Android性能优化-内存泄漏

2020-05-14  本文已影响0人  沉淀者

一、什么是内存泄漏?

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
简单点说就是GC本该被回收的对象却不能被回收而停留在堆内存中。

注意:内存释放主要考虑new出来的这部分对象,这部分对象在堆里面,才会被GC回收。因此内存泄漏主要考虑new出来的对象不用了能不能及时回收掉。

二、出现内存泄漏的原因?

当一个对象已经不再被使用时,本该被回收但却因为有另外一个正在使用的对象持有它的引用从而导致它不能被回收。 这就导致了内存泄漏。

三、常见的内存泄漏有哪些?

1.Context被静态持有

calss Util{
   private static Context context;
   public static void utils(Context context){
       this.context = context;
   }
}

因为context被静态持有后, 生命周期太长, 当context的Activity已经退出时, 应该被回收, 但依然被Util持有, 导致Activity对象的内存无法回收,

解决办法:
1,用Application的context, 类似于Dialog这种必须要Activity的Context的, 可以使用其他方法,
2,不要将context传给静态变量,也不要传给其他生命周期可能会长于Activity的对象

2.非静态内部类

class Activity{
    r = new Runnable(){
        run(){//耗时操作}
    }
    t = new Thread(){}
}

当退出activity, 但耗时操作还未结束时, 就会内存泄漏, 因为new接口在java语法里, 就是创建匿名内部类的意思, java语法里接口是不能new的, 而非静态内部类会持有外部类, 所以Activity类虽然已经结束, 理论应该被GC回收, 但是还在被Runnable持有, 导致内存泄漏

解决方法:

class Activity{
    MyRunnable r = new MyRunnable()
    void create()
    private static class MyThread extends Thread {
    }
    private static class MyRunnable implements Runnable{
    }
}

改成静态内部类, 因为静态内部类, 并不在静态区, 创建出来后就是一个普通的对象, 生命周期跟随普通类

3.静态集合存储对象

Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
    Object o = new Object();
    v.add(o);
    o = null;
}

Vector的生命周期跟随整个应用, 虽然变量名o不再持有对象, 但v一直持有, 导致new出来的对象无法回收, 导致内存泄漏。
解决办法
将v置为null

4.资源未关闭 没有解除注册造成内存泄露

资源性对象比如(Cursor,InputStream/OutputStream,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。

查询数据库时,一定要在finally里面调用cursor.close()保证cursor关闭掉;finally语句之前一定要用catch,要不然不能保证走到finally里面;需要注册的一定不要忘了解除注册,例如,使用BroadcastReceiver时, registerReceiver()之后要调用unregisterReceiver()进行注销;EventBus,适当的时候解除注册;
动画也要及时地停止;

5.Handler引起的内存泄漏

下文着重讲解

四、Handler引起的内存泄漏分析

1.之前我们定义的Handler

// 接收消息,定义一个Handler对象,并实现handleMessage方法
Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        // 在此接收子线程发送的消息
    }
};

//发送消息
 Message msg=new Message();
 msg.obj=receiveData;
 msg.what= MSG_RECEIVEDATA_SUCCEED;
 handler.sendMessage(msg);

2.为什么现在这样定义会引起内存泄漏?

public class HandlerActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        //发送一条10分钟后的消息
        handler.sendEmptyMessageDelayed(0, 10 * 60 * 1000);
        //然后关闭Activity
        finish();
    }

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

使用Handler发送一条延时消息,然后关闭Activity,在Activity被关闭之后,Message将在消息队列中存在10分钟才能被执行到,这个Message持有一个对Handler的引用,又因为Handler是HandlerActivity 的非静态内部类,非静态内部类天然就含有外部类HandlerActivity的引用,因此Handler又持有一个对当前HandlerActivity 的引用,这些引用会在Message被执行之前一直保持,这样当前Activity就不会被垃圾回收机制回收,从而导致内存泄漏。

注意:自己finish掉了叫别人过来回收,但是你还有其他的关联,无法回收你,从而导致内存泄漏。

3.怎么解决内存泄漏?

1)WeakHandler 设置为静态内部类,这样就不会持有对Activity的引用
2)使用弱引用。弱引用的对象拥有短暂的生命周期。在垃圾回收器线程扫描时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存

即在内部类的构造方法中,创建一个对外部类的弱引用,然后再内部类的方法中通过弱引用获取外部类对象,进行非空判断后再进行操作,OK,修改一下我们的代码

public class HandlerActivity extends BaseActivity {

    @Bind(R.id.tv_handler)
    TextView tvHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        ButterKnife.bind(this);

        new WeakHandler(this).sendEmptyMessageDelayed(0, 10 * 60 * 1000);
        finish();
    }

    private static class WeakHandler extends Handler {

        WeakReference<HandlerActivity> weakReference;

        public WeakHandler(HandlerActivity activity) {
            weakReference = new WeakReference<HandlerActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            HandlerActivity activity = weakReference.get();
            if (activity != null && activity.tvHandler != null) {
                activity.tvHandler.setText("收到Handler发送的消息");
            }
        }
    }
}

因为静态内部类不会持有对外部类的引用,所以定义一个静态的Handler,这样Acitivity就不会被泄漏了,同时让Handler持有一个对Activity的弱引用,这样就可以happy的在Handler中调用Activity中的资源或者方法了。

4.最终目的

防止内存泄漏的最终目的就是为了让已经finish的activity被回收掉。上面的做法既保证了handler延时任务的执行,也让HandlerActivity 得以释放。

五、四种引用

强引用(Strong Reference):通常我们通过new来创建一个新对象时返回的引用就是一个强引用,若一个对象通过一系列强引用可到达,它就是强可达的(strongly reachable),那么它就不被回收;(从不回收)

软引用(Soft Reference):若一个对象是软引用可达,软引用可达的对象在内存不充足时才会被回收,因此软引用要比弱引用“强”一些;(内存不足的时候回收)

弱引用(Weak Reference):弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用Weak Reference,若一个对象是弱引用可达,无论当前内存是否充足它都会被回收。

虚引用(Phantom Reference):虚引用是Java中最弱的引用,那么它弱到什么程度呢?它是如此脆弱以至于我们通过虚引用甚至无法获取到被引用的对象,虚引用存在的唯一作用就是当它指向的对象被回收后,虚引用本身会被加入到引用队列中,用作记录它指向的对象已被回收。

六、内存泄漏分析工具

6.1 MAT(Memory Analysis Tools)

通过分析 Java 进程的内存快照 HPROF 分析,快速计算出在内存中对象占用的大小,查看哪些对象不能被垃圾收集器回收 & 可通过视图直观地查看可能造成这种结果的对象

6.2 Heap Viewer

可查看 分别有哪些类型的数据在堆内存总 & 各种类型数据的占比情况

6.3 Allocation Tracker

6.4 Memory Monitor

6.5 LeakCanary

上一篇下一篇

猜你喜欢

热点阅读