ヒープダンプをキャプチャして、キャプチャ時にアプリのどのオブジェクトがメモリを消費しているかを確認し、メモリリークや、スタッタリング、フリーズ、アプリのクラッシュを引き起こすメモリ割り当て動作を特定します。特に、長いユーザー セッションの後にヒープダンプを取得すると、メモリに存在しないと思われるオブジェクトが引き続きメモリにあることが示されるため、ヒープダンプの取得は非常に有用です。
このページでは、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 メモリの合計量(バイト単位)。
保持サイズ: このクラスのすべてのインスタンスのために保持されているメモリの合計サイズ(バイト単位)。
ヒープメニューを使用して、特定のヒープをフィルタします。
- App heap(デフォルト): アプリがメモリを割り当てるプライマリ ヒープ。
- Image heap: 起動時にプリロードされたクラスを含むシステム ブートイメージ。この割り当てが変化することはありません。
- Zygote heap: Android システムでアプリプロセスがフォークされたコピー オン ライト ヒープ。
配置のプルダウンを使用して、割り当ての配置方法を選択します。
- Arrange by class(デフォルト): クラス名に基づいてすべての割り当てをグループ化。
- Arrange by package: パッケージ名に基づいてすべての割り当てをグループ化。
クラスのプルダウンを使用して、クラスのグループをフィルタします。
- すべてのクラス(デフォルト): ライブラリや依存関係のクラスを含む、すべてのクラスが表示されます。
- アクティビティ / フラグメント リークを表示する: メモリリークの原因となっているクラスを表示します。
- プロジェクト クラスを表示: プロジェクトで定義されているクラスのみを表示します。
クラス名をクリックして [Instance] ペインを開きます。それぞれのインスタンスには次のものが含まれます。
- Depth: GC ルートから選択済みのインスタンスへの最小ホップ数。
- Native Size: このインスタンスのネイティブ メモリ内でのサイズ。この列は Android 7.0 以降でのみ表示されます。
- Shallow Size: このインスタンスの Java メモリ内でのサイズ。
- 保持サイズ: このインスタンスがドミネートするメモリのサイズ(ドミネーター ツリーに従う)。
インスタンスをクリックすると、[フィールド] や [参照] などの [インスタンスの詳細] が表示されます。一般的なフィールド型と参照型は、Java の構造化型
、配列
、プリミティブ データ型
です。フィールドまたは参照を右クリックして、ソースコード内の関連するインスタンスまたは行に移動します。
- フィールド: このインスタンスのすべてのフィールドが表示されます。
- References: [Instance] タブでハイライト表示されているオブジェクトへのすべての参照が表示されます。
重複するビットマップを検出する
Android Studio Narwhal 4 以降では、[ヒープダンプ] ビューで冗長なビットマップを検出することもできます。
それらを特定する方法は次のとおりです。
- Android Studio で [Profiler] タブを開く
- [ヒープダンプ](または [メモリ使用量を分析])をクリックし、[Record] をクリックして、アプリの現在のメモリ状態のスナップショットを取得します。
- 分析結果で、黄色の警告三角形 ⚠️ を探します。Android Studio では、重複するビットマップが複数回保存されていることを示すために、この三角形が使用されます。
- または、プロファイラのヘッダーに移動して [Filter by:] を選択し、[Duplicate Bitmaps] 設定を選択します。
- フラグ付きのエントリをクリックすると、[Bitmap Preview] ペインが開き、どの画像が繰り返し違反しているかを正確に確認できます。
- この視覚的な確認結果を使用して、コード内の冗長な読み込みロジックを追跡し、より適切なキャッシュ戦略を実装します。
メモリリークを見つける
メモリリークに関連している可能性のあるクラスをすばやくフィルタするには、クラスのプルダウンを開いて [Show activity/fragment leaks] を選択します。Android Studio には、アプリの Activity インスタンスと Fragment インスタンスでメモリリークが発生していると考えられるクラスが表示されます。
メモリリークを手動で探すには、クラスとインスタンスのリストを参照して、保持サイズが大きいオブジェクトを見つけます。次のいずれかが原因で生じたメモリリークに注目します。
ActivityまたはContextへの長期参照。ホストされている Compose コンポジション グラフ(ComposeViewとそのサブコンポーザブルなど)がリークする可能性があります。- Jetpack Compose の状態オブジェクト(
MutableState)、状態ホルダー、またはContextをキャプチャするラムダのリーク。 DisposableEffectのonDisposeブロックでリスナーまたはオブザーバーをクリーンアップし忘れる。Activityインスタンスを保持できる、Runnableなどの非静的内部クラス。- 必要以上に長くオブジェクトを保持するキャッシュ。
メモリリークの可能性がある箇所を見つけたら、[Instance Details] の [Fields] タブと [References] タブを使用して、目的のインスタンスまたはソースコード行に移動します。
テスト用にメモリリークをトリガーする
メモリ使用量を分析するには、アプリコードに過度な負荷をかけてメモリリークを強制的に発生させる必要があります。アプリでメモリリークを発生させるには、ヒープを調査する前にしばらくの間アプリを実行するという方法があります。リークは、ヒープ内の割り当ての先頭に徐々に出現してきます。ただし、リークが少ない場合は、検出できるまで長時間アプリを実行する必要があります。
次のいずれかの方法で、メモリリークをトリガーすることも可能です。
- さまざまなアクティビティの状態で、デバイスを縦向きから横向きに回転させてから元に戻す動作を何度か行う。アプリが非同期オペレーションまたは状態ホルダー内で
ActivityまたはContextへの参照を保持している場合、デバイスを回転させると、アプリがActivity(およびその結果としてホストされている Compose UI ツリーと関連する状態ツリー)をリークすることがよくあります。 - さまざまなアクティビティの状態で、自分のアプリと別のアプリを切り替える。たとえば、ホーム画面に移動してからアプリに戻ります。
ヒープダンプの記録をエクスポート、インポートする
プロファイラの [Past Recordings] タブから、ヒープダンプ ファイルをエクスポートおよびインポートできます。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