前言

内存泄露(Memory Leak)是指程序在运行过程中,由于疏忽或错误导致已分配的内存空间无法被正确释放,使得这部分内存一直被占用而无法被操作系统回收再利用的现象‌。

这里简单总结一下内存泄露相关知识,记录于此,方便自己查阅和回顾。

本文主要摘抄的,多谢

正文

内存泄露导致程序运行变慢或出现OOM等,内存变少,系统也容易卡死。

原因

由于 Java存在垃圾回收机制(GC),理应不存在内存泄露;出现内存泄露的原因仅仅是外部人为原因 = 无意识地持有对象引用,使得 持有引用者的生命周期 > 被引用者的生命周期

常见的内存泄露的主要原因:

  1. 集合类

  2. static关键字修饰的成员变量

  3. 非静态内部类、匿名类

  4. 资源对象使用后未关闭

场景示例
集合类

集合类添加元素之后,仍引用集合元素对象,导致该集合元素对象不可回收,从而导致内存泄露。

举个例子:

// 通过 循环申请Object 对象 & 将申请的对象逐个放入到集合List
List<Object> objectList = new ArrayList<>();        
for (int i = 0; i < 10; i++) {
    Object o = new Object();
    objectList.add(o);
    o = null;
}

虽释放了集合元素引用的本身:o=null,但但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象。

解决方法:

// 释放objectList
objectList.clear();
objectList=null;
static关键字修饰的成员变量

Static 关键字修饰的成员变量的生命周期 = 应用程序的生命周期

若使被 Static 关键字修饰的成员变量 引用耗费资源过多的实例(如Context),则容易出现该成员变量的生命周期大于引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄露。

举个例子:

public class ClassName {
 // 定义1个静态变量
 private static Context mContext;
 //...
// 引用的是Activity的context
 mContext = context; 
}

当Activity需销毁时,由于mContext = 静态 & 生命周期 = 应用程序的生命周期,故 Activity无法被回收,从而出现内存泄露。

解决方法:

  1. 尽量避免 Static 成员变量引用资源耗费过多的实例(如 Context

    若需引用 Context,则尽量使用ApplicaitonContext

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

非静态内部类、匿名类

若 非静态内部类所创建的实例 = 静态(其生命周期 = 应用的生命周期),会因 非静态内部类默认持有外部类的引用 而导致外部类无法释放,最终造成内存泄露。

举个例子:

// 背景:
   a. 在启动频繁的Activity中,为了避免重复创建相同的数据资源,会在Activity内部创建一个非静态内部类的单例
   b. 每次启动Activity时都会使用该单例的数据
 
public class TestActivity extends AppCompatActivity {  
 
    // 非静态内部类的实例的引用
    // 注:设置为静态  
    public static InnerClass innerClass = null; 
 
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {        
        super.onCreate(savedInstanceState);   
 
        // 保证非静态内部类的实例只有1个
        if (innerClass == null)
            innerClass = new InnerClass();
    }
 
    // 非静态内部类的定义    
    private class InnerClass {        
        //...
    }
}
 
// 造成内存泄露的原因:
// a. 当TestActivity销毁时,因非静态内部类单例的引用(innerClass)的生命周期 = 应用App的生命周期、持有外部类TestActivity的引用
// b. 故 TestActivity无法被GC回收,从而导致内存泄漏

解决方法:

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

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

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

    若需使用 Context,建议使用 ApplicationContext

除了上面还有下面两种容易出现内存泄露。

  1. 多线程(AsyncTask、实现Runnable接口、继承Thread类)内存泄露

    Activity退出时,主动停止线程。

  2. Handler 内存泄露

    Activity退出时,清楚所有广播

    mHandler.removeCallbacksAndMessages(null);

    或者

    //参考文2的代码
    public class MainActivity extends AppCompatActivity {
        public static final String TAG = "carson:";
        private Handler showhandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // 实例化自定义的Handler类对象->>分析1
            // 注:
                // a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
                // b. 定义时需传入持有的Activity实例(弱引用)
            showhandler = new FHandler(this);
    
            new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // a. 定义要发送的消息
                    Message msg = Message.obtain();
                    msg.what = 1;// 消息标识
                    msg.obj = "AA";// 消息存放
                    showhandler.sendMessage(msg);
                }
            }.start();
        }
    
        // 设置为:静态内部类
        private static class FHandler extends Handler{
    
            // 定义 弱引用实例
            private WeakReference<Activity> reference;
    
            // 在构造方法中传入需持有的Activity实例
            public FHandler(Activity activity) {
                // 使用WeakReference弱引用持有Activity实例
                reference = new WeakReference<Activity>(activity); }
    
            // 通过复写handlerMessage() 从而确定更新UI的操作
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        Log.d(TAG, "收到线程1的消息");
                        break;
                    case 2:
                        Log.d(TAG, " 收到线程2的消息");
                        break;
    
    
                }
            }
        }
    }
资源对象使用后未关闭

对于资源的使用(如 广播BraodcastReceiver、文件流File、数据库游标Cursor、图片资源Bitmap等),若在Activity销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏。

解决方法:

// 对于 广播BraodcastReceiver:注销注册
unregisterReceiver()
 
// 对于 文件流File:关闭流
InputStream / OutputStream.close()
 
// 对于数据库游标cursor:使用后关闭游标
cursor.close()
 
// 对于 图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null 
Bitmap.recycle();
Bitmap = null;
 
// 对于动画(属性动画)
// 将动画设置成无限循环播放repeatCount = “infinite”后
// 在Activity退出时记得停止动画

参考文章

  1. Android性能优化(1) 内存泄露 & 解决方案

  2. Android异步通信:详解 Handler 内存泄露的原因

相关文章

暂无评论

none
暂无评论...