內存泄漏解決方案

「內存泄漏」就是一個對象已經不須要再使用了,可是由於其它的對象持有該對象的引用,致使它的內存不能被回收。「內存泄漏」的慢慢積累,最終會致使OOM 「內存溢出」的發生,千里之堤,毀於蟻穴。因此在寫代碼的過程當中,應該要注意規避會致使「內存泄漏」的代碼寫法,提升軟件的健壯性。java

1、常見致使「內存泄漏」的代碼寫法及解決方案android

1.靜態變量引發的內存泄漏數組

在java中靜態變量的生命週期是在類加載時開始,類卸載時結束。換句話說,在android中其生命週期是在進程啓動時開始,進程死亡時結束。因此在程序的運行期間,若是進程沒有被殺死,靜態變量就會一直存在,不會被回收掉。若是靜態變量強引用了某個Activity中變量,那麼這個Activity就一樣也不會被釋放,即使是該Activity執行了onDestroy(不要將執行onDestroy和被回收劃等號)。緩存

這類問題的解決方案爲:1.尋找與該靜態變量生命週期差很少的替代對象。2.若找不到,將強引用方式改爲弱引用。ide

比較典型的例子以下:
單例引發的Context內存泄漏佈局

public class IMManager {
  private Context context;
  private static IMManager mInstance;

  public static IMManager getInstance(Context context) {
    if (mInstance == null) {
      synchronized (IMManager.class) {
        if (mInstance == null)
          mInstance = new IMManager(context);
      }
    }
    return mInstance;
  }

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

}

當調用getInstance時,若是傳入的context是Activity的context。只要這個單例沒有被釋放,這個Activity也不會被釋放。fetch

解決方案this

傳入Application的context,由於Application的context的生命週期比Activity長,能夠理解爲Application的context與單例的生命週期同樣長,傳入它是最合適的。spa

public class IMManager {
  private Context context;
  private static IMManager mInstance;

  public static IMManager getInstance(Context context) {
    if (mInstance == null) {
      synchronized (IMManager.class) {
        if (mInstance == null)
          //將傳入的context轉換成Application的context
          mInstance = new IMManager(context.getApplicationContext());
      }
    }
    return mInstance;
  }

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

}

二、非靜態內部類引發的內存泄漏線程

在java中,建立一個非靜態的內部類實例,就會引用它的外圍實例。若是這個非靜態內部類實例作了一些耗時的操做,就會形成外圍對象不會被回收,從而致使內存泄漏。

這類問題的解決方案爲:1.將內部類變成靜態內部類 2.若是有強引用Activity中的屬性,則將該屬性的引用方式改成弱引用。3.在業務容許的狀況下,當Activity執行onDestory時,結束這些耗時任務。

內部線程形成的內存泄漏

public class LeakAty extends Activity {

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

  public void test() {
    //匿名內部類會引用其外圍實例LeakAty.this,因此會致使內存泄漏
    new Thread(new Runnable() {

      @Override
      public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }).start();
  }
  }

解決方案

將非靜態匿名內部類修改成靜態匿名內部類

public class LeakAty extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.aty_leak);
    test();
  }
  //加上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();
  }
}

Handler引發的內存泄漏

public class LeakAty extends Activity {

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

  }

  private Handler mHandler = new Handler() {
    public void handleMessage(android.os.Message msg) {
      switch (msg.what) {
      case 0:
        // 刷新數據
        break;
      default:
        break;
      }

    };
  };

  private void fetchData() {
    //獲取數據
    mHandler.sendEmptyMessage(0);
  }
}

mHandler 爲匿名內部類實例,會引用外圍對象LeakAty.this,若是該Handler在Activity退出時依然還有消息須要處理,那麼這個Activity就不會被回收。

解決方案

public class LeakAty extends Activity {
  private TextView tvResult;
  private MyHandler handler;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.aty_leak);
    tvResult = (TextView) findViewById(R.id.tvResult);
    handler = new MyHandler(this);
    fetchData();

  }
  //第一步,將Handler改爲靜態內部類。
  private static class MyHandler extends Handler {
    //第二步,將須要引用Activity的地方,改爲弱引用。
    private WeakReference<LeakAty> atyInstance;
    public MyHandler(LeakAty aty) {
      this.atyInstance = new WeakReference<LeakAty>(aty);
    }

    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      LeakAty aty = atyInstance == null ? null : atyInstance.get();
      //若是Activity被釋放回收了,則不處理這些消息
      if (aty == null||aty.isFinishing()) {
        return;
      }
      aty.tvResult.setText("fetch data success");
    }
  }

  private void fetchData() {
    // 獲取數據
    handler.sendEmptyMessage(0);
  }

  @Override
  protected void onDestroy() {
    //第三步,在Activity退出的時候移除回調
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
  }
}

三、資源未關閉引發的內存泄漏
當使用了BraodcastReceiver、File、Cursor、Bitmap等資源時,當不須要使用時,須要及時釋放掉,若沒有釋放,則會引發內存泄漏。

 

四、構造Adapter時,沒有使用緩存的 convertView

以構造ListView的BaseAdapter爲例,在BaseAdapter中提共了方法:

public View getView(intposition, View convertView, ViewGroup parent)

來向ListView提供每個item所須要的view對象。初始時ListView會從BaseAdapter中根據當前的屏幕布局實例化必定數量的view對象,同時ListView會將這些view對象緩存起來。當向上滾動ListView時,原先位於最上面的list item的view對象會被回收,而後被用來構造新出現的最下面的list item。這個構造過程就是由getView()方法完成的,getView()的第二個形參 View convertView就是被緩存起來的list item的view對象(初始化時緩存中沒有view對象則convertView是null)。

由此能夠看出,若是咱們不去使用convertView,而是每次都在getView()中從新實例化一個View對象的話,即浪費時間,也形成內存垃圾,給垃圾回收增長壓力,若是垃圾回收來不及的話,虛擬機將不得不給該應用進程分配更多的內存,形成沒必要要的內存開支。

 

五、一些不良代碼成內存壓力

 

有些代碼並不形成內存泄露,可是它們或是對沒使用的內存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內存,對內存的回收和分配形成很大影響的,容易迫使虛擬機不得不給該應用進程分配更多的內存,增長vm的負擔,形成沒必要要的內存開支。

如Bitmap使用不當

第1、及時的銷燬。

雖然,系統可以確認Bitmap分配的內存最終會被銷燬,可是因爲它佔用的內存過多,因此極可能會超過Java堆的限制。所以,在用完Bitmap時,要及時的recycle掉。recycle並不能肯定當即就會將Bitmap釋放掉,可是會給虛擬機一個暗示:「該圖片能夠釋放了」。

第2、設置必定的採樣率。

有時候,咱們要顯示的區域很小,沒有必要將整個圖片都加載出來,而只須要記載一個縮小過的圖片,這時候能夠設置必定的採樣率,那麼就能夠大大減少佔用的內存。以下面的代碼:

private ImageView preview;
BitmapFactory.Options options = newBitmapFactory.Options();
options.inSampleSize = 2;//圖片寬高都爲原來的二分之一,即圖片爲原來的四分之一
Bitmap bitmap =BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
preview.setImageBitmap(bitmap);   

總結

  • 對 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銷燬時及時關閉或者註銷。
  • 保持對對象生命週期的敏感,特別注意單例、靜態對象、全局性集合等的生命週期。
相關文章
相關標籤/搜索