美文网首页
Android内存泄漏

Android内存泄漏

作者: 忘尘无憾 | 来源:发表于2018-09-11 11:38 被阅读14次

内存泄漏,简单而言就是该被释放的对象没有被释放掉,一直被某个或某些实例引用,导致无法被GC回收。

Java的内存分配策略

Java程序运行时,内存分配策略有三种:静态分配、栈分配、堆分配。所对应的内存空间即为:静态存储区(方法区)、栈区、堆区

  • 静态储存区:编译时就分配好,在程序整个运行期间都存在。它主要存放静态数据和常量;

  • 栈区:当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存;

  • 堆区:通常存放 new 出来的对象。由 Java 垃圾回收器回收。

局部变量的基本数据和引用存储在栈中,引用的实体存储在堆中。
因为他们属于方法中的变量,生命周期随方法而结束。

成员变量全部存储在堆中(包括基本数据、引用和引用的实体对象)。
因为他们属于类,类对象被new的时候,会存储在堆中。

Java中如何管理内存

  • 内存分配:程序控制,new 对象申请内存空间(基本类型除外),所有对象都在堆中分配空间。
  • 垃圾回收:GC完成内存的释放。
Java内存管理

Java使用有向图的方式进行内存管理,可以消除引用循环的问题。如果某个对象与这个根顶点不可达,那么可以认为这个(这些)对象不再被引用,可以被 GC 回收。

还有一个常见的内存管理技术就是使用引用计数器。虽然执行效率高,但是精度低(很难处理循环引用问题。)

Java中的内存泄漏

  • 这些对象是可达的。
  • 这些对象是无用的。
  • 即因为可达而不会被GC回收,但是又无用占据了内存,因此造成了内存泄漏。
Java中的内存泄漏

Android中常见的内存泄漏

集合中对象没清理造成的内存泄漏

我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
所以要在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

解决方案:

  • 在Activity退出之前,将集合里的东西clear,然后置为null,再退出程序。

单例类造成的内存泄漏

单例的静态特性导致其生命周期同应用一样长。

解决方法:

  • 将该属性的引用方式改为弱引用;
  • 如果传入Context,使用ApplicationContext;
/* 例1:在activity中使用该单例时会传入context,这会延长Context的存活时间。
 * 如果传入的是activity context,当activity销毁时,
 * 该activity不会被GC回收,从而导致了内存泄漏。
 */
public class AppManager {

    private static AppManager instance;
    private Context context;

    private AppManager(Context context) {
        this.context = context;
    }

    public static AppManager getInstance(Context context) {
        if (instance == null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}
// **************************
/* 解决方法
 * 1. 使用Applicaion Context 代替 Activity Context 
 * 2. 在调用的地方使用弱引用
 */
private AppManager(Context context) {
    this.context = context.getAppcalition();
}
// 或
WeakReference<MainActivity> activityReference = new WeakReference<MainActivity>(this);
Context context = activityReference.get();
if(context != null){
    AppManager.getInstance(context);
    // ...
}
/* 例2:在调用该单例时,点击view会传入view。
 * 当该view被销毁时,由于静态的单例持有view的引用而GC无法回收,从而导致了内存泄漏。
 */
private static ScrollHelper mInstance;    
private ScrollHelper() {
}    
public static ScrollHelper getInstance() {
    if (mInstance == null) {
       synchronized (ScrollHelper.class) {  
            if (mInstance == null) {
                mInstance = new ScrollHelper();
            }
        }
    }        
    return mInstance;
}    
/**
 * 被点击的view
 */
private View mScrolledView = null;    
public void setScrolledView(View scrolledView) {
    mScrolledView = scrolledView;
}

/* 解决方法
 * 使用弱引用
 */
private WeakReference<View> mScrolledViewWeakRef = null;    
public void setScrolledView(View scrolledView) {
    mScrolledViewWeakRef = new WeakReference<View>(scrolledView);
}

非静态或匿名内部类造成的内存泄漏

在Java中,非静态内部类匿名类 都会潜在的引用它们所属的外部类,但是,静态内部类却不会。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。

解决方案:

  • 将内部类变成静态内部类;
  • 如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用;
  • 在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务;
/* 由于非静态内部类或匿名内部类都会拥有所在外部类的引用。
 * 当 Activity 销毁后,该匿名内部类还在执行任务,
 * 导致外部的 Activity 不能被回收,导致内存泄露。
 */
public class LeakAct extends Activity {  
    @Override
    protected void onCreate(Bundle savedInstanceState) {    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aty_leak);
        test();
    } 
    //这儿发生泄漏    
    public void test() {    
        new Thread(new Runnable() {      
            @Override
            public void run() {        
                while (true) {          
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

/* 解决方法:
 * 静态化匿名内部类
 */

//加上static,变成静态匿名内部类
public static void test() {    
    new Thread(new Runnable() {     
        @Override
        public void run() {        
            while (true) {          
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();
}
// 如果是内部类,静态化内部类
static class MyThread extends Thread {
    @Override
    public void run() {
        // ...
    }
}

静态内部类持有外部引用造成的内存泄漏

虽然静态内部类的生命周期和外部类无关,但是如果在内部类中想要引入外部成员变量的话,这个成员变量必须是静态的了,这也可能导致内存泄露。

解决方法: 使用弱引用。

public class MainActivity extends Activity {  

    private static WeakReference<MainActivity> activityReference;
    @Override
    protected void onCreate(Bundle savedInstanceState) {    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        activityReference = new WeakReference<MainActivity>(this);;
    }  

    private static class MyThread extends Thread {
        @Override
        public void run() {
           // 耗时操作
           MainActivity activity = activityReference.get();
           if(activity != null){
               activity...
           }
        }
    }
}

Handle造成的内存泄漏

当Handler中有延迟的的任务或是等待执行的任务队列过长,由于消息持有对Handler的引用,而Handler又持有对其外部类的潜在引用,这条引用关系会一直保持到消息得到处理,而导致了Activity无法被垃圾回收器回收,而导致了内存泄露。

解决方案:

  • 可以把Handler类放在单独的类文件中,或者使用静态内部类便可以避免泄露;

  • 如果想在Handler内部去调用所在的Activity,那么可以在handler内部使用弱引用的方式去指向所在Activity。使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。

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

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);
    // Go back to the previous Activity.
    finish();
  }
}

/* 解决方法:
 * 使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。
 */
public class MainActivity extends Activity {

    private final MyHandler mHandler = new MyHandler(this);

    private static final Runnable mRunnable = new Runnable() {
        @Override
        public void run() { /* ... */ }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mHandler.postDelayed(mRunnable, 1000 * 60 * 10);
        finish();
    }

    private static class MyHandler extends Handler {
        private final WeakReference<MainActivity> mActivityReference;

        public MyHandler(MainActivity activity) {
            mActivityReference = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = mActivityReference.get();
            if (activity != null) {
                // ...
            }
        }
    }
}

static 修饰activity或间接修饰activity context

由于静态变量和应用存活时间是相同的,直接或间接修饰了activity很荣耀造成内存泄漏问题。

解决方案:

  • 不使用static修饰
  • 在适当的地方将其置空
  • 弱引用

Activity Context 的不正确使用造成的内存泄漏

在Android应用程序中通常可以使用两种Context对象:Activity和Application。当类或方法需要Context对象的时候常见的做法是使用第一个作为Context参数。这样就意味着View对象对整个Activity保持引用,因此也就保持对Activty的所有的引用。

解决方案:

  • 使用Application Context代替Activity Context,因为Application Context会随着应用程序的存在而存在,而不依赖于activity的生命周期;

  • 对Context的引用不要超过它本身的生命周期,慎重的对Context使用“static”关键字。Context里如果有线程,一定要在onDestroy()里及时停掉。

注册监听器造成的内存泄漏

系统服务可以通过Context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果Context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有Activity 的引用,如果在Activity onDestory时没有释放掉引用就会内存泄漏。

解决方案:

  • 使用ApplicationContext代替ActivityContext;

  • 在Activity执行onDestory时,调用反注册;

/* 例1
 * Context.getSystemService获取服务会让服务持有Activity 的引用
 */
mSensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);

// 解决
mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);

资源性对象造成的内存泄漏

Cursor,Stream没有close,View没有recyle都有可能造成内存泄漏。

在不使用他们时,应该及时关闭他们,以便它们的缓冲及时回收内存。

如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。

虽然系统在回收它时也会关闭它,但是这样的效率太低了。

总结

  • 对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。
  • 尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
  • 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
    • 将内部类改为静态内部类
    • 静态内部类中使用弱引用来引用外部类的成员变量
  • Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.
  • 在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
  • 正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
  • 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。

参考:

相关文章

网友评论

      本文标题:Android内存泄漏

      本文链接:https://www.haomeiwen.com/subject/vquygftx.html