ヒープダンプをキャプチャする

ヒープダンプをキャプチャして、キャプチャ時にアプリ内のどのオブジェクトがメモリを消費しているかを確認し、メモリリークや、スタッター、フリーズ、さらにはアプリのクラッシュにつながるメモリ割り当て動作を特定します。長時間のユーザー セッションの後にヒープダンプを取ると、メモリに存在しないはずのオブジェクトがメモリに残っていることが判明することがあります。

このページでは、Android Studio が提供するヒープダンプの収集と分析を行うためのツールについて説明します。または、コマンドラインで dumpsys を使ってアプリのメモリを調べたり、Logcat でガベージ コレクション(GC)イベントを表示したりすることもできます。

アプリのメモリをプロファイリングする理由

Android は管理メモリ環境を備えています。アプリが一部のオブジェクトを使用していないことが検出されると、ガベージ コレクターは未使用のメモリをリリースしてヒープに戻します。Android が未使用のメモリを検出する仕組みは継続的に改善されていますが、すべての Android バージョンにおいて、ある時点でコードの実行を短時間一時停止する必要があります。ほとんどの場合、そういった一時停止は知覚できません。システムがメモリを収集できる以上の速度でアプリがメモリを割り当てると、コレクターが十分なメモリを解放して割り当てを満たしているにもかかわらず、アプリで遅延が発生する場合があります。この遅延により、アプリがフレームをスキップしたり、明らかに速度が低下したりする場合があります。

アプリで速度低下が発生していない場合でも、メモリリークが発生していれば、アプリはバックグラウンドにあってもメモリを保持します。これにより、不必要なガベージ コレクション イベントが強制的に実行され、システム上の残りのメモリ パフォーマンスが低下する可能性があります。最終的には、システムによりアプリプロセスが強制終了され、メモリが回収されます。その後、ユーザーがアプリに戻ったとき、アプリのプロセスを完全に再起動する必要があります。

アプリのメモリ使用量を削減できるプログラミング方法については、アプリのメモリを管理するをご覧ください。

ヒープダンプの仕組み

ヒープダンプをキャプチャするには、[Analyze Memory Usage (Heap Dump)] タスクを選択します([Profiler: run 'app' as debuggable (complete data)] を使用してヒープダンプをキャプチャします)。ヒープをダンプする際に Java メモリの使用量が一時的に増加する場合があります。これは通常の現象です。ヒープダンプはアプリと同じプロセスで発生し、データを収集するためにいくらかのメモリを要求するからです。ヒープダンプをキャプチャすると、次のように表示されます。

クラスのリストには、次の情報が表示されます。

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

  • Shallow Size: このオブジェクト タイプによって使用されている Java メモリの合計量(バイト単位)。

  • Retained Size: このクラスのすべてのインスタンスのために保持されているメモリの合計サイズ(バイト単位)。

ヒープ メニューを使用して、特定のヒープに対してフィルタリングします。

  • アプリヒープ(デフォルト): アプリがメモリを割り当てるプライマリ ヒープ。
  • イメージ ヒープ: 起動時にプリロードされたクラスを含むシステム ブートイメージ。この割り当てが変化することはありません。
  • Zygote heap: Android システムでアプリプロセスがフォークされたコピー オン ライト ヒープ。

[配置] プルダウンを使用して、割り当ての配置方法を選択します。

  • クラス別に並べ替え(デフォルト): クラス名に基づいてすべての割り当てをグループ化します。
  • Arrange by package: パッケージ名に基づいてすべての割り当てをグループ化。

クラスのプルダウンを使用して、クラスのグループにフィルタします。

  • すべてのクラス(デフォルト): ライブラリや依存関係のクラスを含むすべてのクラスが表示されます。
  • アクティビティ/フラグメントのリークを表示: メモリリークの原因となっているクラスが表示されます。
  • プロジェクト クラスを表示: プロジェクトで定義されたクラスのみを表示します。

クラス名をクリックして [インスタンス] ペインを開きます。表示される各インスタンスには次のものが含まれます。

  • Depth: GC ルートから選択済みのインスタンスへの最小ホップ数。
  • Native Size: このインスタンスのネイティブ メモリ内でのサイズ。この列は Android 7.0 以降でのみ表示されます。
  • Shallow Size: このインスタンスの Java メモリ内でのサイズ。
  • Retained Size: このインスタンスがドミネートするメモリのサイズ(ドミネーター ツリーに従う)。

インスタンスをクリックして、フィールド参照など、[インスタンスの詳細] を表示します。一般的なフィールド型と参照型は、Java の構造化型 、配列 、プリミティブ データ型 です。フィールドまたは参照を右クリックすると、ソースコード内の関連するインスタンスまたは行に移動します。

  • フィールド: このインスタンス内のすべてのフィールドが表示されます。
  • 参照: [インスタンス] タブでハイライト表示されているオブジェクトへのすべての参照が表示されます。

メモリリークを見つける

メモリリークに関連している可能性のあるクラスに簡単にフィルタするには、クラスのプルダウンを開き、[アクティビティ/フラグメントのリークを表示] を選択します。Android Studio には、アプリの Activity インスタンスと Fragment インスタンスでメモリリークが発生していると考えられるクラスが表示されます。フィルタで表示されるデータの種類は、次のとおりです。

  • 破棄されているが、まだ参照されている Activity インスタンス。
  • 有効な FragmentManager がないが、まだ参照されている Fragment インスタンス。

次のような状況では、フィルタによって誤検出が発生することがあります。

  • Fragment が作成されたが、まだ使用されていない。
  • Fragment がキャッシュされているが、FragmentTransaction の一部ではない。

メモリリークをより手動で探すには、クラスリストとインスタンスリストを参照して、保持サイズが大きいオブジェクトを見つけます。次のいずれかが原因で生じたメモリリークに注目します。

  • ActivityContextViewDrawable と、Activity または Context コンテナへの参照を保持している可能性があるその他のオブジェクトへの長期的参照。
  • Activity インスタンスを保持できる、Runnable などの非静的内部クラス。
  • 必要以上に長くオブジェクトを保持するキャッシュ。

メモリリークの可能性がある場合は、[インスタンスの詳細] の [フィールド] タブと [参照] タブを使用して、目的のインスタンスまたはソースコード行に移動します。

テスト用にメモリリークをトリガーする

メモリ使用量を分析するには、アプリコードに過度な負荷をかけてメモリリークを強制的に発生させる必要があります。アプリでメモリリークを発生させるには、ヒープを調査する前にしばらくの間アプリを実行するという方法があります。リークは、ヒープ内の割り当ての先頭に徐々に出現してきます。ただし、リークが少ない場合は、検出できるまで長時間アプリを実行する必要があります。

次のいずれかの方法で、メモリリークをトリガーすることも可能です。

  • さまざまなアクティビティ状態で、デバイスを縦向きから横向きに回転させてから元に戻す動作を何度か行う。デバイスを回転させると、ActivityContext、または View オブジェクトのリークがアプリで発生することがよくあります。これは、システムにより Activity が再作成され、これらのいずれかのオブジェクトへの参照がどこか別の場所で保持されている場合、システムはこのようなオブジェクトをガベージ コレクションの対象にできないためです。
  • さまざまなアクティビティ状態で、自分のアプリと別のアプリを切り替えます。たとえば、ホーム画面に移動してからアプリに戻ります。

ヒープダンプ レコードのエクスポートとインポート

ヒープダンプ ファイルは、プロファイラの [過去の録画] タブからエクスポートとインポートできます。Android Studio は、録音を .hprof ファイルとして保存します。

jhat など別の .hprof ファイル アナライザーを使用する場合は、.hprof ファイルを Android 形式から Java SE .hprof ファイル形式に変換する必要があります。ファイル形式を変換するには、{android_sdk}/platform-tools/ ディレクトリで提供されている hprof-conv ツールを使用します。元の .hprof ファイル名と、変換後の .hprof ファイルの書き込み先(新しい .hprof ファイル名を含む)の 2 つの引数を指定して、hprof-conv コマンドを実行します。次に例を示します。

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