メモリリークソリューション

8725 ワード

「メモリリーク」は、オブジェクトが使用される必要はありませんが、他のオブジェクトがオブジェクトの参照を持っているため、メモリが回収されません.「メモリリーク」が徐々に蓄積され、最終的にはOOMの「メモリオーバーフロー」が発生し、千里の堤防が蟻の穴に破壊される.だから、コードを書く過程で、「メモリ漏れ」を招くコードの書き方を避け、ソフトウェアの頑丈性を高めることに注意しなければならない.
一、よくある「メモリ漏れ」のコード書き方と解決方案
1.静的変数によるメモリリーク
Javaでの静的変数のライフサイクルは、クラスのロード時に開始し、クラスのアンインストール時に終了します.すなわちandroidでは、プロセスの開始時にライフサイクルが開始され、プロセスの死亡時に終了する.したがって,プログラムの実行中にプロセスが殺されなければ静的変数は常に存在し,回収されない.静的変数がActivityの変数を強く参照している場合、このActivityは同様に解放されません.ActivityがonDestroyを実行しても(onDestroyを実行したり回収したりしないでください).
このような問題の解決策は:1.この静的変数のライフサイクルとあまり差のない代替オブジェクトを探します.2.見つからない場合は、強引用方式を弱引用に変更します.
比較的典型的な例は以下の通りである:一例による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も解放されない.
ソリューション
アプリケーションに入力されるcontextは、アプリケーションのcontextのライフサイクルがActivityよりも長いため、アプリケーションの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)
          //    context   Application context
          mInstance = new IMManager(context.getApplicationContext());
      }
    }
    return mInstance;
  }

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

}

2、静的でない内部クラスによるメモリ漏れ
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);
  }
}

3、リソースがオフになっていないことによるメモリリークBraodcastReceiver、File、Cursor、Bitmapなどのリソースを使用している場合、使用する必要がない場合は、速やかにリリースする必要があり、リリースしていない場合はメモリリークを引き起こす.
 
4、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()の2番目のパラメータView convertViewはキャッシュされたlist itemのviewオブジェクト(初期化時にキャッシュにviewオブジェクトがなければconvertViewはnull)である.
これにより、convertViewを使用するのではなく、getView()でビューオブジェクトを再インスタンス化するたびに、時間の無駄、メモリゴミの回収に圧力がかかり、ゴミの回収が間に合わない場合、仮想マシンはアプリケーションプロセスにより多くのメモリを割り当てなければならず、不要なメモリ支出をもたらすことがわかります.
 
5、不良コードによるメモリ圧力
 
一部のコードはメモリの漏洩をもたらさないが、使用されていないメモリを有効かつタイムリーに解放していないか、既存のオブジェクトを有効に利用していないのではなく、頻繁に新しいメモリを申請し、メモリの回収と割り当てに大きな影響を与え、仮想マシンがアプリケーションプロセスにより多くのメモリを割り当てなければならず、vmの負担を増加させやすい.不要なメモリコストが発生します.
Bitmapの不適切な使用
第一に、タイムリーな廃棄.
Bitmapで割り当てられたメモリが最終的に破棄されることを確認できますが、メモリが多すぎるため、Javaスタックの制限を超える可能性があります.そのため、Bitmapを使い切るときは、タイムリーにrecycleを落とす必要があります.recycleはすぐにBitmapを解放するとは確信していませんが、仮想マシンに「この画像は解放できる」というヒントを与えます.
第二に、一定のサンプリングレートを設定する.
表示する領域が小さく、画像全体をロードする必要がなく、縮小した画像を記載するだけで、一定のサンプリングレートを設定することができ、消費メモリを大幅に削減することができます.次のコードに従います.
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のライフサイクル内に制御されるべきである.Activityが外部のライフサイクルのオブジェクトによって参照されて漏洩しないように、getApplicationContextまたはgetApplicationを使用することを考慮できない場合.
  • 静的変数または静的内部クラスで非静的外部メンバー変数(contextを含む)を使用しないでください.使用する場合でも、外部メンバー変数を適時に空にすることを考慮してください.外部クラスの変数を参照するために、内部クラスで弱いインデックスを使用することもできます.
  • Activityよりも長いライフサイクルを持つ内部クラスオブジェクトに対して、内部クラスに外部クラスのメンバー変数が使用されているため、メモリ漏洩を回避できます.
  • 内部クラスを静的内部クラス
  • に変更する.
  • 静的内部クラスで外部クラスを参照するために弱い引用を使用するメンバー変数
  • .
  • Handlerの持つ参照オブジェクトは弱い参照を使用することが望ましいが,リソース解放時にHandlerの中のメッセージをクリアすることもできる.例えばActivity onStopやonDestroyの場合、そのHandlerオブジェクトのMessageとRunnableをキャンセルする.
  • Javaの実装では、オブジェクトの解放も考慮しなければならない.最良の方法は、あるオブジェクトを使用しない場合、Bitmapを使用した後にrecycle()を呼び出し、null、ピクチャなどのリソースに直接参照または間接参照の配列を空にすることである(array.clear()を使用する).array=null)など、誰が誰を作成して解放するかという原則に従うのが望ましい.
  • リソースを正しくシャットダウンし、BraodcastReceiver、ContentObserver、File、カーソルCursor、Stream、Bitmapなどのリソースを使用した場合は、Activity破棄時に直ちにシャットダウンまたはログアウトする必要があります.
  • は、オブジェクトのライフサイクルに敏感なままであり、特に、単例、静的オブジェクト、グローバル集合などのライフサイクルに注意する.