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

ヒープダンプをキャプチャして、キャプチャ時にアプリのどのオブジェクトがメモリを使い切っているかを確認し、メモリリーク(スタッタリング、フリーズ、さらにはアプリのクラッシュを引き起こすメモリ割り当て動作)を特定します。特に、長いユーザー セッションの後にヒープダンプを取得すると、メモリに存在しないと思われるオブジェクトが引き続きメモリにあることが示されるため、メモリリークの特定に役立ちます。

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

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

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

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

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

ヒープダンプの概要

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

Android Studio Profiler のヒープダンプ ビュー。

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

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

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

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

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

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

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

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

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

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

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

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

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

  • フィールド: このインスタンスのすべてのフィールドが表示されます。
  • References: [Instance] タブでハイライト表示されているオブジェクトへのすべての参照が表示されます。
Heap Dump ツール ウィンドウの [インスタンス]、[フィールド]、[参照] ビュー。

メモリリークを見つける

メモリリークに関連付けられている可能性があるクラスをすばやくフィルタするには、クラスのプルダウンを開き、[Show activity/fragment leaks] を選択します。Android Studio は、アプリの Activity インスタンスと Fragment インスタンスでメモリリークが発生していると考えられるクラスを表示します。

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

  • ホストされた Compose コンポジション グラフ(ComposeView やそのサブコンポーザブルなど)をリークする可能性がある Activity または Context への長期間の参照。
  • Jetpack Compose の状態オブジェクト(MutableState)、状態ホルダー、または Context をキャプチャするラムダのリーク。
  • DisposableEffectonDispose ブロックでリスナーまたはオブザーバーをクリーンアップし忘れている。
  • Activity インスタンスを保持できる、Runnable などの非静的内部クラス。
  • 必要以上に長くオブジェクトを保持するキャッシュ。

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

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

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

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

  • さまざまなアクティビティ状態で、デバイスを縦向きから横向きに回転させてから元に戻す動作を何度か行う。デバイスを回転させると、非同期オペレーションまたは状態ホルダー内で Activity または Context への参照が保持されている場合、アプリで Activity(およびそのホストされた Compose UI ツリーと関連する状態ツリー)のリークが発生することがよくあります。
  • さまざまなアクティビティの状態で、自分のアプリと別のアプリを切り替えます。たとえば、ホーム画面に移動してからアプリに戻ります。

ヒープダンプの記録をエクスポート、インポートする

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

別の .hprof ファイル アナライザー(jhat など)を使用するには、.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

参考情報