English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Android Studio+MATでメモリリークの実戦

Androidでは、メモリリークに注意しないと、特にActivityでは比較的簡単に発生します以下に、自分がどのようにメモリリークを検出するかを説明します。

まず、メモリリークとは何でしょうか?

メモリリークは、既に使用されていないオブジェクトがメモリに存在し、ゴミ回収機構がそれらを回収できないため、常駐してメモリの消費が増加し、最終的にはプログラムのパフォーマンスが悪化します
Androidバーチャルマシンではルートノード検索アルゴリズムを使用してルートノードを列挙し、ゴミかどうかを判定しますバーチャルマシンはGC Rootsから始めてノードを巡回し、GC Rootsに到達する道が見つからないノード、つまりGC Rootsと接続していないノードがある場合、その参照は無効であり、回収可能ですメモリリークは、悪い呼び出しにより、無駄なオブジェクトとGC Rootsが接続され、回収できないため、メモリリークが発生します

メモリリークが何かを知ったら、当然ながら避け方は分かりますそれはコードを書くときに無駄なオブジェクトに対する長期間の参照を避けることです簡単に言えばそうですが、十分な経験が必要で達成することが難しいですので、メモリリークは比較的簡単に発生します完全に避けることは難しいので、プログラム中に発生したメモリリークを見つけ、修正する必要があります
以下にメモリリークを発見する方法について説明します。

メモリリークの検出:

例えば以下のこのコード:

public class MainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    String string = new String();
  }
  public void click(View view){
    Intent intent = new Intent();
    intent.setClass(getApplicationContext(), SecondActivity.class);
    startActivity(intent);
  }
}
public class SecondActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(8000000L);
        catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };
    new Thread(runnable).start();
  }
}

 

このActivityにジャンプするたびにスレッドが呼び出され、そのスレッドはRunnableのrunメソッドを実行しますRunnableはアニミング内部オブジェクトなのでSecondActivityの参照を持ちますそれで簡単な2つのActivityでMainActivityからSecondActivityにジャンプすることができます以下、MainActivityからSecondActivityにジャンプし、その後SecondActivityからMainActivityに戻るように続けます5次に、MainActivityに戻ると、SecondActivityが破棄・再利用されるべきですが、実際にはそうではないことがあります。

この時、メモリオーバーフローの発生を判断するには、ツールを使用する必要があります。以下に2つの方法があります。

1を使用してMATで検索

まず、ASのAndroid Device Monitorツールを開きます。以下の図を参照してください:


開くと以下の画面が表示されます:


まず、検証したいアプリケーションのパッケージ名を選択し、以下の図に囲まれた場所をクリックします。アプリケーションパッケージ名の後ろにアイコンがマークされます。


次に、アプリケーションを操作して、アプリケーションを交互にジャンプさせる必要があります。5次。

次に、以下の図に示されるアイコンをクリックすると、hprofファイルを分析するためにエクスポートします。

エクスポートされたファイルは以下のようになります:

hprofファイルを取得すると、MATツールを使用して分析することができます。

MATツールを開きます。

ダウンロードできなければ、以下のURLからダウンロードできます:

MATツールのダウンロード先

以下の図に示されるように、インターフェースが表示されます:

私たちが以前にエクスポートしたhprofファイルを開くと、予想外に以下のエラーが表示されます:

MATはjavaプログラムのhprofファイルを分析するために使用されます。Androidからエクスポートされるhprofには一定のフォーマットの違いがあります。したがって、エクスポートされたhprofファイルを変換する必要があります。SDKには、hprofを変換するツールが提供されています。-conv.exeは以下の場所にあります:


次に、このパスにcdしてこのコマンドを実行し、hprofファイルを変換します。以下の図を参照してください:


その中で、hprof-conv コマンドの使い方

hprof-conv 源ファイル 输出文件

例えば、hprof-conv E:\aaa.hprof E:\output.hprof

aaa.hprofをoutput.hprofに変換し、出力します。output.hprofが私たちが変換したファイルです。図のmat2.hprofが私たちが変換したファイルです。

次に、MATツールを使用して、変換したmat2.hprofファイルを開きます。エラーが表示されないようにすると、以下のようになります:


それでは、現在のメモリに存在するオブジェクトを確認できます。私たちのメモリリークは一般的にActivityで発生するため、Activityのみを検索する必要があります。

以下の図にマークされたQQLアイコンをクリックし、selectを入力します。 * from instanceof android.app.Activity

SQL文句に似た Activityに関連する情報を検索します。赤い感嘆符をクリックして実行すると、以下のようになります:

次に、以下にフィルタリングされたActivity情報が見られます

上図のように、メモリにまだ存在しています 6個のSecondActivityインスタンスがありますが、すべて退出したい場合は、メモリリークが発生していることを示しています

ここにはShallow sizeとRetained Sizeの2つの属性があります

Shallow Size
オブジェクト自身が占めるメモリサイズは、参照するオブジェクトを含まないです。非配列型のオブジェクトの場合、サイズはオブジェクトとそれのすべてのメンバ変数の合計サイズです。
もちろん、これにはJava言語の特性のデータストレージユニットも含まれます。配列型のオブジェクトの場合、サイズは配列要素のオブジェクトの合計サイズです。
Retained Size
Retained Size=現在のオブジェクトのサイズ+現在のオブジェクトが直接または間接的に参照できるオブジェクトの合計サイズ。(間接参照の意味:A->B->C, Cは間接参照です)
ただし、解放する際にはGC Rootsが直接または間接的に参照するオブジェクトを除外する必要があります。彼らは一時的にGarbageと見なされません。

次に、SecondActivityを右クリックします


すべての参照を含めて選択してください

以下の図に示されるページを開いてください

以下の図のページを確認してください

このthis0が参照していることを確認してくださいActivityそしてthis0は内部クラスを表す意味があり、つまり内部クラスがActivityを参照し、this$0がtargetに参照され、targetがスレッドであるため、原因が見つかりました。メモリリークの原因は、Activityが内部クラスに参照され、内部クラスがスレッドによって使用されるため、解放することができません。そのため、このクラスのコードを見てみましょう。

public class SecondActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(8000000L);
        catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };
    new Thread(runnable).start();
  }
}
確かに

確かにSecondActivity内にRunnableの内部クラスオブジェクトがあり、その後スレッドが使用し、スレッドが実行する8000秒 そのためSecondActivityオブジェクトが参照されて解放できず、メモリオーバーフローが発生しました。

このようなメモリオーバーフローを解決するには、Activityが退出する際にスレッドを終了すること(ただし、非常に難しい場合もあります)、またはスレッドの実行時間を適切に制御することが重要です。

これでこのプログラムのメモリオーバーフローを見つけました。

2Android StudioのMonitor Memoryを直接利用してメモリオーバーフローを検出します。

それでも上記のプログラムを使って、簡単に説明します。

まず、モバイルデバイスでプログラムを実行し、ASのMonitorのMemoryエリアを開いてMemory画像を確認してください。

小トラックアイコン(図中)をクリックしてください。1位置アイコン(図中)をクリックすることで一度のGCをトリガーできます。


図中をクリックしてください。2位置アイコンでhprofファイルを確認できます。

左側はメモリ内のオブジェクトで、Activityが存在するかどうかを調べて、既に解放された希望するActivityが存在するかどうかを確認します。もし期待通りに解放されたActivityが存在する場合、右側をクリックするとその合計数が表示され、右側のどれかをクリックするとそのGC Rootsの木構造図が表示され、関係図を確認することでメモリリークの発生場所を見つけることができます(最初の方法に似ています)。

これでメモリリークの検出が完了しました。

Androidでは、メモリリークが発生する原因は以下のようなものに大別されます:

1静的変数によるメモリリーク

静的変数のライフサイクルはクラスのロード時に始まり、クラスのアンロード時に終わるため、静的変数はプログラムのプロセスが死亡するまで解放されません。静的変数の中でActivityが参照されている場合、そのActivityは参照されているため、静的変数のライフサイクルと同じように常に解放されず、メモリリークが発生します。

解決策:

Activityが静的変数で参照されている場合、getApplicationContextを使用してください。なぜならApplicationのライフサイクルはプログラムの開始から終了まで、静的変数と同じです。

2スレッドによるメモリリーク

上記の例と同様の状況で、スレッドの実行時間が長く、Activityが抜け出しても実行されるため、スレッドまたはRunnableがActivityの内部クラスであるため、Activityのインスタンスを保持しています(内部クラスの作成には外部クラスが必要であるため)、その結果、Activityが解放できません。

AsyncTaskにはスレッドプールがありますが、問題がさらに深刻です

解決策:

1スレッドの実行時間を適切に計画し、スレッドがActivityが終了する前に終了するようにします。

2内部クラスを静的内部クラスに変更し、Activityインスタンスを保存するためにWeakReferenceを使用します。なぜなら、弱い参照はGCが発見するとすぐに回収されるため、迅速に回収できます。

3BitMapが多くのメモリを占有します

bitmapの解析にはメモリが必要ですが、メモリは提供されています8Mの空間をBitMapに割り当てます。画像が多くてBitMapを適切にrecycleしないとメモリオーバーフローの発生が考えられます。

解決策:

画像をロードする前に画像をrecycleして圧縮します

4リソースが適切に閉じられていないことで発生するメモリリーク

例えば、Cursorが適切にcloseされないとActivityの参照を保存し、メモリリークが発生します

解決策:

onDestroyメソッドで適切にcloseします

5Handlerの使用で発生するメモリリーク

Handlerの使用では、handlerがmessageオブジェクトをMessageQueueに送信し、LooperがMessageQueueをループしてMessageを取得し実行しますが、もしMessageが長時間取得されない場合、MessageにHandlerの参照があり、Handlerは通常内部クラスオブジェクトであり、MessageがHandlerを参照し、HandlerがActivityを参照しているため、Activityが回収できません。

解決策:

静的内部クラスを使用し続けます+弱い参照の方法で解決できます

中には集合オブジェクトが未削除で、登録されたオブジェクトがアンレジストされていない場合、コードの負荷が高くなることでメモリリークが発生する可能性がありますが、上記の解決策を用いると大抵は解決できます。

おすすめ