Android 内存泄漏

名词解释

内存泄漏:即memory leak。是指内存空间使用完毕后无法被释放的现象,虽然Java有垃圾回收机制(GC),但是对于还保持着引用, 该内存不能再被分配使用,逻辑上却已经不会再用到的对象,垃圾回收器不会回收它们。

内存溢出:即out of memory, 当你要求分配的内存超过了系统给你的内存时, 系统就会抛出out of memory的异常(每个Android能用的内存是有限的) 。比如: 当前应用只剩下4M的空间可用, 但你却加载得到一个需要占用5M空间的图片Bitmap对象, 就会抛出溢出的异常

常见内存泄漏场景&解决方案

1.非静态内部类、匿名类()

非静态内部类会持有外部类的引用,如果非静态内部类的实例是静态的,就会长期的维持着外部类的引用,阻止被系统回收。

解决方案是使用静态内部类

1.1非静态内部类

非静态内部类(non static inner class)和 静态内部类(static inner class)之间的区别。

如果非静态内部类所创建的实例是静态的,其生命周期等于应用的生命周期。非静态内部类默认持有外部类的引用而导致外部类无法释放,最终造成内存泄露。即外部类中持有非静态内部类的静态对象。

public class MainActivity extends Activity {
    //非静态内部类的静态实例引用
    public static TestClass testClass = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //保证非静态内部类的实例只有1个
        if (testClass == null) {
            testClass = new TestClass();
        }
    }

    // 非静态内部类
    private class TestClass {
        //todo something
    }
}

当 MainActivity 销毁时,因非静态内部类单例的引用,testClass 的生命周期等于应用的生命周期,持有外部类 MainActivity 的引用,故 MainActivity 无法被 GC 回收,从而导致内存泄漏。

解决方案:

将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)

该内部类抽取出来封装成一个单例

尽量避免非静态内部类所创建的实例是静态的

1.2 多线程相关的匿名内部类和非静态内部类(继承 Thread 类、实现 Runnable 接口、AsyncTask)

当子线程正在处理任务时,如果外部类销毁, 由于子线程实例持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄露。

public class MainActivity extends Activity {
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyThread().start();
    }

    private class MyThread extends Thread {
        @Override
        public void run() {
            //todo someting
        }
    }
}

解决方案:

  1. 使用静态内部类的方式,静态内部类不默认持有外部类的引用。
   private static class MyThread extends Thread {
           @Override
           public void run() {
               //todo someting
           }
       }
  1. 当外部类结束生命周期时(即Activity或Fragment),强制结束线程(onDestroy或onDestroyView)。使得工作线程实例的生命周期与外部类的生命周期同步。

     @Override
     protected void onDestroy() {
         super.onDestroy();
         myThread.interrupt();
     }
    
  2. Handler内存泄漏(重新理解为什么 Handler 可能导致内存泄露?)

Handler内部message是被存储在MessageQueue中的,MessageQueue中的 Message 持有 Handler 实例的引用,有些message不能马上被处理,存在的时间会很长,导致handler无法被回收,如果handler是非静态的(内部类、匿名内部类),默认持有外部类的引用(如 MainActivity 实例),导致它的外部类无法被回收。

public class MainActivity extends Activity {
    private MyHandler myHandler;

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

        myHandler = new MyHandler();
        new Thread() {
            @Override
            public void run() {
                try {
                    //执行耗时操作
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //发送消息
                myHandler.sendEmptyMessage(1);
            }
        }.start();
    }

    private class MyHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            //处理消息事件
        }
    }
}

解决方案:

  1. 使用静态内部类+弱引用的方式,保证外部类能被回收。因为弱引用的对象拥有短暂的生命周期,在垃圾回收器线程扫描时,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

    public class MainActivity extends Activity {
    private MyHandler myHandler;

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
    
         myHandler = new MyHandler(this);
         new Thread() {
             @Override
             public void run() {
                 try {
                     //执行耗时操作
                     Thread.sleep(10000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 //发送消息
                 myHandler.sendEmptyMessage(1);
             }
         }.start();
     }
    
     public void test() {
         Log.d("TAG", "test");
     }
    
     private static class MyHandler extends Handler {
         //定义弱引用实例
         private WeakReference<Activity> reference;
    
         //在构造方法中传入需持有的Activity实例
         public MyHandler(Activity activity) {
             //使用 WeakReference 弱引用持有 Activity 实例
             reference = new WeakReference<Activity>(activity);
         }
    
         @Override
         public void handleMessage(Message msg) {
             //处理消息事件
             //调用Activity实例中的方法
             ((MainActivity) reference.get()).test();
         }
     }
    

    }

  2. 当外部类结束生命周期时,清空 Handler 内消息队列。

     @Override
     protected void onDestroy() {
         if (myHandler!= null) {
             myHandler.removeCallbacksAndMessages(null);
         }
         super.onDestroy();
     }
    
  3. Context导致内存泄漏

根据场景确定使用Activity的Context还是Application的Context,因为二者生命周期不同,对于不必须使用Activity的Context的场景(Dialog),一律采用Application的Context,单例模式是最常见的发生此泄漏的场景,比如传入一个Activity的Context被静态类引用,导致无法回收

  1. 静态View导致泄漏

使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致无法回收。

解决方案:

尽量避免 static 成员变量引用资源耗费过多的实例(如 Context),若需引用 Context,则尽量使用Applicaiton的 Context。

使用弱引用(WeakReference) 代替强引用持有实例。

在Activity销毁的时候将静态View设置为null

5.资源对象未关闭导致

对于资源若在 Activity 销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏。

如广播、文件、Bitmap、数据库等使用

//对于广播BroadcastReceiver:注销注册
unregisterReceiver(broadcastReceiver);

//对于文件流File:关闭流
inputStream / outputStream.close();

//对于数据库游标cursor:使用后关闭游标
cursor.close();

//对于图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null 
bitmap.recycle();
bitmap = null;

// 对于动画(属性动画),将动画设置成无限循环播放setRepeatCount(ValueAnimator.INFINITE);后
// 在Activity退出时记得停止动画
animator.cancel();

6.监听器未关闭

很多需要register和unregister的系统服务要在合适的时候进行unregister,手动添加的listener也需要及时移除

7.集合中的对象未清理

集合用于保存对象,如果集合越来越大,不进行合理的清理,

  1. WebView导致的内存泄漏(目前没有遇到)

WebView只要使用一次,内存就不会被释放,所以WebView都存在内存泄漏的问题。

通常的解决办法是为WebView单开一个进程,使用AIDL进行通信,根据业务需求在合适的时机释放掉

内存泄漏分析工具

lint

lint 是一个静态代码分析工具,同样也可以用来检测部分会出现内存泄露的代码,平时编程注意 lint 提示的各种黄色警告即可。如:

也可以手动检测,在 Android Studio 中选择 Code->Inspect Code。

然后会弹出选择检测范围

点击Ok,等待分析结果

这个工具除了会检测内存泄漏,还会检测代码是否规范、是否有没用到的导包、可能的bug、安全问题等等。

Memory Profile :Memory Profile 的使用

LeakCanary :LeakCanary

相关推荐
儿时可乖了2 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol3 分钟前
java基础概念37:正则表达式2-爬虫
java
I_Am_Me_9 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
重生之我是数学王子19 分钟前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
xmh-sxh-131420 分钟前
jdk各个版本介绍
java
Ai 编码助手21 分钟前
使用php和Xunsearch提升音乐网站的歌曲搜索效果
开发语言·php
学习前端的小z25 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
神仙别闹33 分钟前
基于C#和Sql Server 2008实现的(WinForm)订单生成系统
开发语言·c#
XINGTECODE34 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
天天扭码39 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构