Memory Profiler でアプリのメモリ使用量を検査する

The Memory Profiler は Android Profiler のコンポーネントで、メモリ リークやメモリ回転を特定し、吃音、フリーズ、およびアプリのクラッシュにつながる可能性さえあります。

Memory Profiler を開くには、次の手順に従います。

  1. [View] > Tool Windows > Profiler (ツール バーの [Profile] もクリックできます。) をクリックします。
  2. MEMORY タイムラインのどこかをクリックして、Memory Profiler を開きます。

あるいは、コマンド ラインから撤退したumpsys でアプリのメモリを検査し、logcat で GC イベントを確認することもできます。

Why you should profile your app memory

Android は管理されたメモリ環境を提供します。アプリケーションがもはやいくつかのオブジェクトを使用していないと判断すると、ガベージコレクターは未使用のメモリをヒープに解放します。 Android では、未使用のメモリをどのように見つけるかは常に改善されていますが、すべての Android バージョンで、システムはコードを一時停止する必要があります。 ほとんどの場合、この一時停止は意識する必要はありません。 しかし、アプリケーションが、システムが回収するよりも速くメモリを割り当てた場合、コレクターが割り当てを満たすのに十分なメモリを解放する間、アプリケーションが遅延する可能性があります。 この遅延により、アプリがフレームをスキップして、目に見える遅さを引き起こす可能性があります。

アプリが遅さを示さない場合でも、メモリをリークすると、バックグラウンドにいる間でもそのメモリを保持することがあります。 この動作は、不要なガベージ コレクション イベントを強制的に発生させるため、システムの残りのメモリ パフォーマンスを低下させる可能性があります。 最終的に、システムはアプリのプロセスを強制終了してメモリを回収することになります。

  • パフォーマンスの問題を引き起こしている可能性のあるタイムラインでの望ましくないメモリ割り当てパターンを探します。
  • Record memory allocations during normal and extreme user interaction to identify exactly where your code is either allocating too many objects in ashort time or allocating objects that become leaked.

    Memory Profiler の概要

    Memory Profiler を最初に開くと、アプリのメモリ使用の詳細なタイムラインが表示され、ガベージ コレクションを強制する、ヒープ ダンプをキャプチャする、およびメモリ割り当てを記録するツールにアクセスすることができます。

    1. ガベージ コレクション イベントを強制的に発生させるボタン。

      注意: メモリー割り当てを記録するボタンは、Android 7.1 (API レベル 25) 以下を実行しているデバイスに接続されている場合のみ、ヒープ ダンプ ボタンの右側に表示されます。

    2. プロファイラーがメモリー割り当てを記録する頻度を設定するドロップダウン メニューです。 適切なオプションを選択することで、プロファイリング中にアプリのパフォーマンスを向上させることができます。
    3. タイムラインを拡大/縮小するボタン。
    4. ライブ メモリ データにジャンプするボタン。
    5. イベント タイムライン。アクティビティの状態、ユーザー入力イベント、画面の回転イベントなどを表示するボタン。
    6. メモリ使用タイムラインには以下が含まれます。
      • 左側の Y 軸と上部のカラー キーで示される、各メモリ カテゴリーで使用されているメモリ量の積み上げグラフです。
      • 破線は、右側の Y 軸で示される、割り当てられたオブジェクトの数を示します。 Advancedprofiling is unavailable for the selected process” というメッセージが表示された場合、Advanced Profiling を有効にして、以下を確認する必要があります:
        • Event timeline
        • Number of allocated objects
        • Garbage collection event

        Android 8.1092>

        How memory is counted

        Memory Profiler (図 2) の上部に表示される数字は、Android のシステムに従って、アプリケーションがコミットしているすべてのプライベート メモリ ページに基づいています。 このカウントには、システムや他のアプリと共有するページは含まれません。

        図 2. メモリプロファイラの上部にあるメモリ数の凡例

        メモリ数のカテゴリは以下の通りです:

        • Java: Java: Java または Kotlin コードから割り当てられたオブジェクトのメモリ。
        • Native: Java または Kotlin コードから割り当てられたオブジェクトのメモリ。 C または C++ コードから割り当てられたオブジェクトのメモリ。

          アプリで C++ を使用していない場合でも、ここでネイティブ メモリが使用されていることがあります。 GL サーフェスや GL テクスチャなど、画面にピクセルを表示するためのグラフィックス バッファ キューに使用されるメモリ。 (これは CPU と共有されるメモリであり、専用の GPU メモリではないことに注意してください。)

        • Stack: アプリ内のネイティブおよび Java スタックの両方で使用されるメモリ。 これは通常、アプリが実行しているスレッド数に関連します。

        • Code: dex バイトコード、最適化またはコンパイルされた dex コード、.so ライブラリ、およびフォントなどです。

        • Allocated: システムが分類する方法を知らない、アプリによって使用されるメモリ。 アプリが割り当てた Java/Kotlin オブジェクトの数です。 これは、CまたはC++で割り当てられたオブジェクトをカウントしません。

          Android7.1以下を実行しているデバイスに接続している場合、この割り当て数は、Memory Profilerが実行中のアプリに接続した時点でのみ開始されます。 そのため、プロファイリングを開始する前に割り当てられたオブジェクトは考慮されません。 しかし、Android 8.0 以降では、すべての割り当てを記録するデバイス上のプロファイリングツールが含まれているため、この数字は常に Android 8.0 以降のアプリで未処理の Java オブジェクトの合計数を表します。

        以前の Android Monitor ツールのメモリカウントと比較すると、新しい Memory Profiler はメモリを異なる方法で記録するので、メモリ使用率が高いように感じるかもしれません。 Java の数値は、おそらく AndroidMonitor で見た数値と正確には一致しませんが、新しい数値は、Zygote からフォークされて以来、アプリの Java ヒープに割り当てられたすべての物理メモリ ページを考慮に入れています。

        View memory allocations

        Memory allocations は、メモリ内の各 Java オブジェクトおよび JNI 参照がどのように割り当てられたかを表示します。 具体的には、Memory Profiler は、オブジェクトの割り当てについて以下を示すことができます。

      • オブジェクトがいつ割り当て解除されたか (Android 8.0 以上のデバイスを使用している場合のみ)。

      Android 8.0 以上のデバイスを実行している場合、以下の方法でいつでもオブジェクト割り当てを確認することができます。 タイムラインをドラッグして、割り当てを表示したい領域を選択します(ビデオ1)。 Android 8.0 以降には、アプリの割り当てを常に追跡するオンデバイス プロファイリング ツールが含まれているため、録画セッションを開始する必要はありません。 Android 8.0 以降では、既存のタイムライン領域を選択してオブジェクトの割り当てを表示します

      お使いのデバイスが Android 7.1 以下を実行している場合は、メモリプロファイラー ツールバーのメモリ割り当ての記録 をクリックします。 記録中、メモリプロファイラがアプリで発生したすべての割り当てを追跡します。 記録が終了したら、[記録の停止](同じボタン。ビデオ2を参照)をクリックして、割り当てを表示します。 Android 7.1 以下では、明示的にメモリ割り当てを記録する必要があります

      タイムラインの領域を選択した後 (または Android 7.1 を実行しているデバイスで録画セッションを終了したとき)、メモリ割り当てを確認します。

      割り当て記録を確認するには、次の手順に従います。

      1. リストを参照して、異常に大きなヒープカウントを持つオブジェクトやリークされている可能性のあるオブジェクトを見つけます。 既知のクラスを見つけるには、クラス名列のヘッダーをクリックして、アルファベット順に並べ替えます。 次に、クラス名をクリックします。 図 3 に示すように、右側にインスタンス表示ペインが表示され、そのクラスの各インスタンスが表示されます。
        • また、フィルタをクリックするか、Control+F (Mac では Command+F) を押して、検索フィールドにクラス名またはパッケージ名を入力すると、すばやくオブジェクトを見つけることができます。 また、ドロップダウンメニューから「コールスタックで並べる」を選択すると、メソッド名で検索することもできます。 正規表現を使用する場合は、「Regex」の横にあるチェックボックスをオンにします。
      2. インスタンスビューペインで、インスタンスをクリックします。
      3. [Call Stack] タブで、任意の行を右クリックして [Jump to Source] を選択し、エディタでそのコードを開きます。 割り当てられた各オブジェクトの詳細は、右側のインスタンス ビューに表示されます。

        割り当てられたオブジェクトのリストの上にある 2 つのメニューを使用して、検査するヒープとデータを整理する方法を選択できます。 システムによってヒープが指定されていない場合。

      4. image heap。 システムのブートイメージで、ブート時にプリロードされるクラスが含まれています。 ここでの割り当ては、決して移動したり消えたりしないことが保証されている。 アプリプロセスが Android システムでフォークされるコピーオンライトヒープ。 アプリがメモリを割り当てる主なヒープ。
      5. JNI ヒープ。

右側のメニューから、割り当てを整理する方法を選択します。 クラス名に基づいてすべての割り当てをグループ化します。 これはデフォルトです。

  • パッケージで並べます。 パッケージ名に基づいてすべての割り当てをグループ化します。
  • コールスタックによってアレンジします。
  • Improve app performance while profiling

    App パフォーマンスを向上させるために、メモリプロファイラーはデフォルトでメモリ割り当てを定期的にサンプリングします。 APIlevel 26以上を実行しているデバイスでテストする場合、[Allocation Tracking]ドロップダウンを使用して、この動作を変更することができます。 利用可能なオプションは次のとおりです:

    • Full: メモリ内のすべてのオブジェクトの割り当てをキャプチャします。 これは、Android Studio 3.2およびそれ以前のバージョンでのデフォルトの動作です。 多くのオブジェクトを割り当てるアプリを使用している場合、プロファイリング中にアプリの速度が目に見えて低下することがあります。 メモリ内のオブジェクトの割り当てを一定間隔でサンプリングします。 これはデフォルトのオプションで、プロファイリング中のアプリのパフォーマンスへの影響は少なくなっています。
    • Off:アプリのメモリ割り当ての追跡を停止します。

    View global JNI references

    Java Native Interface (JNI) は、Java コードとネイティブ コードが互いに呼び合うことを可能にするフレームワークです。 JNI 参照が最初に明示的に削除されずに破棄された場合、Java ヒープ上の一部のオブジェクトは到達不能になる可能性があります。

    このような問題をトラブルシューティングするには、メモリ プロファイラの JNI ヒープ ビューを使用して、すべてのグローバル JNI 参照を参照し、Java タイプおよびネイティブ コールスタックによってフィルタリングします。 この情報により、いつ、どこでグローバル JNI 参照が作成および削除されたかを見つけることができます。

    アプリケーションの実行中に、タイムラインの検査したい部分を選択し、クラス リストの上のドロップダウン メニューから JNI ヒープを選択します。それから、図 4 に示すように、通常と同様にヒープ内のオブジェクトを検査し、[Allocation Call Stack] タブのオブジェクトをダブルクリックして、JNI 参照がコード内で割り当てられ解放される場所を確認できます。 グローバル JNI 参照の表示

    アプリの JNI コードのメモリ割り当てを検査するには、アプリを Android 8.0 以降を実行するデバイスにデプロイする必要があります。

    Native Memory Profiler

    Android Studio Memory Profiler には、Android 10 を実行している物理デバイスにデプロイされたアプリの Native Memory Profiler が含まれています。 選択された期間中に malloc() または new 演算子で割り当てられたオブジェクトのカウントです。 選択された期間中にfree()またはdeleteオペレータによって割り当て解除されたオブジェクトのカウントです。 選択された期間中のすべての割り当てのバイト単位の集計サイズです。

  • 割り当て解除サイズ。 選択された期間中のすべての解放されたメモリのバイト単位のサイズの集計です。 割り当て列の値から解放列の値を引いた値です。
  • 残りサイズです。
  • 記録を開始するには、Memory Profilerウィンドウの上部にある[ネイティブ割り当てを記録する]をクリックします。 32 バイトのメモリが割り当てられるたびに、メモリのスナップショットが取得されます。 サンプルサイズを小さくすると、スナップショットの頻度が高くなり、メモリ使用量についてより正確なデータが得られます。 サンプルサイズを大きくすると、正確なデータは得られませんが、システム上のリソースの消費が少なくなり、記録中のパフォーマンスが向上します。

    Native Memory Profiler のサンプルサイズを変更するには:

    1. [実行 > 設定を編集する] を選択します。
    2. Profiling タブをクリックし、Native memory sampling interval (bytes) フィールドにサンプル サイズを入力します。
    3. Build and run your app again.

    Capture a heap dump

    heap dump は、アプリ内でヒープを取得した時点でメモリを使用するオブジェクトを表示します。 特に、長時間のユーザー セッションの後、ヒープ ダンプは、もはや存在しないはずのオブジェクトがまだメモリ内にあることを示すことにより、メモリ リークの特定に役立ちます。

    ヒープ ダンプを取得すると、以下を表示することができます。

  • 各オブジェクトが使用しているメモリ量。
  • 各オブジェクトへの参照がコード内で保持されている場所。 (コールスタックは、現在Android 7.1以下で、割り当てを記録しながらヒープダンプを取得した場合のみ利用できます。)
  • ヒープダンプを取得するには、メモリプロファイラのツールバーで[Javaヒープのダンプ]をクリックします。これは、ヒープ ダンプがアプリケーションと同じプロセスで発生し、データを収集するためにいくつかのメモリを必要とするためです。

    ヒープ ダンプは、図 5 に示すように、メモリ タイムラインの下に表示され、ヒープ内のすべてのクラス タイプを表示します。 ヒープ ダンプの表示

    いつダンプが作成されたかをより正確に知る必要がある場合、dumpHprofData() を呼び出して、アプリケーション コード内の重要なポイントでヒープ ダンプを作成することができます。 ヒープ内の割り当て数.

  • Native Size.Allocations(割り当て数):ヒープ内の割り当て数。 このオブジェクトタイプが使用するネイティブメモリの総量(バイト)。 この列は、Android 7.0 以降で表示されます。

    Android では、Bitmap などの一部のフレームワーク クラスのネイティブ メモリを使用するため、Java で割り当てられた一部のオブジェクトのメモリがここに表示されます。 このオブジェクトタイプによって使用されるJavaメモリの合計量(バイト単位)

  • Retained Size。

  • 割り当てられたオブジェクトのリストの上にある 2 つのメニューを使用して、検査するヒープ ダンプを選択し、データを整理する方法を選択できます。 システムによってヒープが指定されていない場合。

  • app heap。 アプリがメモリを割り当てる主要なヒープ。 ブート時にプリロードされるクラスが含まれるシステムブートイメージ。 ここでの割り当ては、決して移動したり消えたりしないことが保証されています。
  • zygote ヒープ。
  • 右側のメニューから、割り当てを整理する方法を選択します。 クラス名に基づいてすべての割り当てをグループ化します。 これはデフォルトです。

  • パッケージで並べます。 パッケージ名に基づいてすべての割り当てをグループ化します。
  • コールスタックによってアレンジします。 すべての割り当てを対応するコールスタックにグループ化します。 このオプションは、割り当てを記録している間にヒープ・ダンプをキャプチャした場合にのみ機能します。 それでも、記録を開始する前に割り当てられたオブジェクトがヒープにある可能性が高いので、それらの割り当てが最初に表示され、単にクラス名でリストされます。
  • リストは、デフォルトで保持サイズ列でソートされます。

    クラス名をクリックすると、右側にインスタンスビューウィンドウが表示されます(図6参照)。

    • Depth:
    • Native Size: この列は、Android 7.0以降で表示されます。
    • Shallow Size: ネイティブメモリ内のこのインスタンスのサイズです。 Javaメモリにおけるこのインスタンスのサイズです。
    • Retained Size:

    図6.保持サイズ:このインスタンスが(ドミネーターツリーに従って)支配するメモリのサイズ。 ヒープダンプをキャプチャするのに必要な時間は、タイムライン

    ヒープを検査するには、次の手順に従います。 既知のクラスを見つけるには、[クラス名] 列のヘッダーをクリックしてアルファベット順に並べ替えます。 次に、クラス名をクリックします。

    • あるいは、[フィルタ] をクリックするか、Control+F (Mac では Command+F) を押して、検索フィールドにクラス名またはパッケージ名を入力すると、すばやくオブジェクトを見つけることができます。 また、ドロップダウンメニューから「コールスタックで並べる」を選択すると、メソッド名で検索することもできます。 正規表現を使用する場合は、「Regex」の横にあるチェックボックスをオンにします。
  • インスタンスビューペインで、インスタンスをクリックします。

    または、インスタンス名の横にある矢印をクリックしてそのすべてのフィールドを表示し、フィールド名をクリックしてそのすべての参照を表示します。

  • [References] タブで、メモリをリークしている可能性がある参照を特定したら、それを右クリックして [Go to Instance] を選択します。
    • Activity, Context, View, Drawable, および Activity または Context コンテナへの参照を保持する可能性があるその他のオブジェクトへの長時間の参照です。
    • Activity インスタンスを保持できる Runnable などの非静的な内部クラス。
    • 必要以上に長くオブジェクトを保持するキャッシュ。 プロファイリング・セッションを終了すると、ヒープ・ダンプは失われます。 そのため、後で確認するために保存したい場合は、ヒープダンプをHPROFファイルにエクスポートしてください。 Android Studio 3.1以下では、ツールバーの左側、タイムラインの下にExport capture to fileボタンがあり、Android Studio 3.2以上では、セッションペインの各ヒープダンプエントリの右側にExport Heap Dumpボタンがあります。 Export Asdialogで、ファイル名の拡張子を.hprofとして保存します。

      jhatのような別のHPROF解析器を使うには、HPROFファイルをAndroidフォーマットからJava SE HPROFフォーマットへ変換する必要があります。 hprof-convコマンドに、変換前のHPROFファイルと変換後のHPROFファイルを書き込む場所を指定します。 例えば、

      hprof-conv heap-original.hprof heap-converted.hprof

      ヒープダンプファイルをインポートする

      HPROF(.hprof)ファイルをインポートするには、セッションペインの新しいプロファイルセッションの開始をクリックして、ファイルから読み込みを選択し、ファイルブラウザからファイルを選択します。

      Leak detection in Memory Profiler

      Memory Profiler でヒープ ダンプを解析する場合、アプリ内の Activity および Fragment インスタンスのメモリ リークを示すかもしれないと Android Studio が考えるプロファイル データをフィルターすることが可能です。

      • Activity 破壊されたがまだ参照されているインスタンス。
      • Fragment 有効な FragmentManager を持っていないがまだ参照されているインスタンス。

      以下のような特定の状況では、フィルターが falsepositives を生成する可能性があります。 メモリをリークしている可能性のあるフラグメントやアクティビティを表示するには、図 7 に示すように、Memory Profiler のヒープダンプペインで Activity/Fragment Leaks チェックボックスを選択します。 メモリ リークのためのヒープ ダンプのフィルタリング。

      メモリ プロファイリングのテクニック

      メモリ プロファイラを使用している間、アプリケーション コードにストレスを与え、メモリ リークを発生させてみる必要があります。 アプリケーションでメモリ リークを誘発する 1 つの方法は、ヒープを検査する前に、しばらく実行させることです。 リークは、ヒープ内の割り当ての最上位まで上昇する可能性があります。 しかし、リークが小さいほど、それを確認するためにアプリを長時間実行する必要があります。

      • 異なる動作状態の間にデバイスを縦向きから横向きに何度も回転させる。 デバイスを回転させると、ActivityContext、または View オブジェクトがリークすることがよくあります。これは、システムが Activity を作成し、アプリがこれらのオブジェクトへの参照を他のどこかで保持していると、システムがそれをガベージ コレクションできないからです。
      • 異なるアクティビティ状態のときに、自分のアプリと別のアプリを切り替える (ホーム画面に移動し、自分のアプリに戻る).

      Tip: Tonkeyrunner testframework.

      を使用して、上記の手順を実行することも可能です。

  • コメントを残す

    メールアドレスが公開されることはありません。