Heap Dump(ヒープダンプ)は、アプリケーションの動的メモリのスナップショットであり、すべてのライブオブジェクトに関する完全な情報を含みます:そのクラス、サイズ、相互参照、GCルートからの到達可能性。 Heap Dump は、メモリリークの分析とリソース消費の最適化のための主要なツールです。Android Developers によると、heap dump分析は、循環参照、忘れられたリスナー、解放されていない静的参照を含む、最大95%のメモリリークを検出できます。
重要ポイント
Heap dump は、仮想マシンのヒープの完全なダンプです — すべての動的に作成されたオブジェクトが存在するメモリ領域。Java と Kotlin では Android の Dalvik/ART ヒープ、Swift と Objective-C では iOS の ARC 管理ヒープです。Heap dump は、すべてのオブジェクト、そのクラス、サイズ、フィールド、他のオブジェクトへの参照、GC ルートからの到達可能性フラグをキャプチャします。
Heap dump の主な目的は、メモリリークの検出です。リークは、アプリケーションが不要になったオブジェクトへの参照を保持し続け、ガベージコレクタによる収集を妨げるときに発生します。典型的な原因:アクティビティ破棄時に登録解除されていないイベントリスナー、コンテキストへの参照を持つシングルトン、self をキャプチャするクロージャ、削除なしでデータが追加される静的コレクション。Heap dump は正確な状況を提供します:どのオブジェクトが “生きている” か、どれが不要か、そして誰がそれらを参照しているか。
Google I/O によると、Android アプリのクラッシュレポートの60%以上が OutOfMemoryError に関連しており、80%のケースで根本原因は heap dump で検出可能なメモリリークです。iOS アプリでも状況は同様で、retain cycle によるリークはクラッシュの最も一般的な原因の1つであり、Xcode の Allocations インストルメントで特定されます。
Heap dump は、次の症状が現れたときに実行する必要があります:アプリケーションが繰り返し操作(画面間の前後移動)中にメモリを線形に消費する;画面を閉じた後もメモリがベースラインレベルに戻らない;OutOfMemoryError または iOS のメモリ警告が発生する;メモリ制限超過によりアプリケーションが終了する。Instagram や Spotify のような大規模モバイルプロジェクトでは、定期的な heap dump 収集がエンジニアリング文化プロトコルの一部です。
Android Studio は Memory Profiler を提供します — リアルタイムでヒープダンプをキャプチャするための組み込みツール。View → Tool Windows → Profiler からアクセスできます。アプリケーションを起動後、セッションを選択し、Memory タブに移動して Dump Java Heap をクリックします。Android Studio はアプリケーションを一時停止し、ART ヒープダンプを実行し、分析用に結果をロードします。ダンプファイルは .hprof 形式 — ほとんどのメモリアナライザと互換性のある HPROF 標準です。
ダンプをロードした後、Android Studio は列を持つオブジェクトテーブルを表示します:Allocations(インスタンス数)、Native Size(ART ヒープ外のメモリ)、Shallow Size(オブジェクト自身のメモリ)、Retained Size(サブグラフ全体を含むオブジェクトのメモリ)。クラス名によるフィルタリング、retained size による並べ替え、パッケージによる検索により、問題領域を迅速に見つけることができます。
// 典型的なリーク — onDestroy で登録解除されていないリスナー
class MainActivity : AppCompatActivity() {
private val sensorManager by lazy {
getSystemService(SENSOR_SERVICE) as SensorManager
}
private val listener = MySensorListener()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sensorManager.registerListener(listener,
sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),
SensorManager.SENSOR_DELAY_NORMAL)
}
override fun onDestroy() {
super.onDestroy()
// ❌ sensorManager.unregisterListener(listener) がない
// → Activity が GC されず、heap dump がリークを表示する
}
}
Dominator Tree タブは、最も多くのメモリを保持しているオブジェクトを表示します。オブジェクトが dominator tree から削除されると、そのオブジェクトが保持していたすべてのメモリがガベージコレクションの対象になります。これは重要なツールです:数千のオブジェクトをスキャンする代わりに、メモリの80〜90%を制御する10〜20のオブジェクトに焦点を当てます。Google によると、dominator tree 分析はリークポイントを見つける最も効果的な方法であり、分析時間を数時間から数分に短縮します。
Xcode Instruments は、ヒープダンプを扱うための2つのツールを提供します:Allocations — リアルタイム消費グラフ付きのヒープダンプキャプチャ;Leaks — retain cycle 分析による自動リーク検出。Allocations は、ヒープ内のすべてのオブジェクト、そのサイズ、作成数(allocations)、解放数(deallocations)を表示します。特定のクラスの作成数と解放数の差は、潜在的なリークを示します。
Allocations でのヒープダンプキャプチャは、Snapshot Memory ボタンで行います — ツールがアプリケーションを一時停止し、完全なダンプを取得します。その後、標準ビューが利用可能です:クラス別のオブジェクトリスト、各オブジェクトのコールツリー、レポートジェネレーター。Android Studio とは異なり、Xcode は .hprof を使用せず、Instruments と互換性のある独自の .trace 形式でデータを保存します。
// 典型的な iOS リーク — クロージャによる retain cycle
class NetworkManager {
var onComplete: ((Data) -> Void)?
func startRequest() {
// ❌ クロージャが self をキャプチャ — retain cycle
onComplete = { data in
self.process(data)
}
}
func process(_ data: Data) {}
}
Leaks インストルメントは、参照グラフ分析を通じて retain cycles とリークを自動的に検出します。リークしているオブジェクトを紫色のアイコンでマークし、ルート(GC root)へのパスを表示します。retain cycle を解消するには、クロージャキャプチャに [weak self] または [unowned self] を追加するだけです。Swift を使用するチームでは、Leaks インストルメントの定期的な実行は CI パイプラインの必須ステップです。
// 修正 — self への弱参照
onComplete = { [weak self] data in
guard let self else { return }
self.process(data)
}
Heap dump を正しく分析するには、3つの主要メトリクスを理解する必要があります。Shallow size は、オブジェクトが直接占有するメモリ量:そのフィールド、ヘッダー、アライメント。典型的な Java/Kotlin オブジェクトの場合、shallow size は16〜40バイトです。Retained size は、オブジェクトの shallow size と、このオブジェクトを通してのみ到達可能なすべてのオブジェクトの合計 shallow size です。Retained size は、メモリ消費に対するオブジェクトの実際の影響を示します。
| メトリクス | 説明 | 例 |
|---|---|---|
| Shallow size | オブジェクト自体のサイズ(バイト) | Bitmap (100×100) = 40,016 B |
| Retained size | Shallow size + 保持するすべて | View Tree を持つ Activity = 2〜5 MB |
| Deep size | Retained size + 他のグラフからのネストされたオブジェクト | アダプター付き ScrollView = 10〜50 MB |
Dominator tree は、各オブジェクトがその “支配者” を参照する構造です — その到達可能性を制御するオブジェクト。支配者が削除されると、そのサブツリーのすべてのオブジェクトがガベージになります。Dominator tree 分析は、どのオブジェクトが最も多くのメモリを保持しているかを見つける最速の方法です。Eclipse MAT によると、90%のリークは5分で top-20 の dominator tree をレビューすることで検出されます。
Heap dump によるリーク分析のプロセスは、いくつかのステップで構成されます。ステップ1:メモリを解放するアクションを実行します(画面を閉じる、操作を完了する)。ステップ2:GC を呼び出し、heap dump を取得します。ステップ3:破棄されるべきだったオブジェクトを見つけます。ステップ4:疑わしいオブジェクトについて、Path to GC Roots を実行します — オブジェクトを生かし続けている参照の連鎖。連鎖の最後の参照がリークの原因です。
Path to GC Roots 機能は、Android Studio Profiler、Eclipse MAT、Xcode Instruments で利用できます。GC ルートから問題のオブジェクトまでの最短の参照チェーンを表示します。弱い参照とソフト参照を除外すると、ガベージコレクションを実際に妨げている強い参照のみが得られます。Square Engineering によると、Android アプリのリークの70%は、Activity または Context への静的参照と、登録されたが登録解除されていないリスナーのわずか2つのパターンによって引き起こされます。
// 静的参照によるリークの例
object AppCache {
private val cache = mutableMapOf<String, Any>()
fun storeActivityReference(activity: Activity) {
cache["current_activity"] = activity // ❌ リーク!
}
}
// 修正:弱参照
object AppCacheFixed {
private val cache = mutableMapOf<String, WeakReference<Any>>()
}
比較モードの手法は、リークを見つける最も効果的な方法の1つです。繰り返しアクションの前後にヒープダンプを取得します。主要クラスのインスタンス数を比較します:すべてのアクティビティが閉じられたにもかかわらず Activity の数が増えた場合 — それはリークです。Android Studio と Eclipse MAT は、差分を強調表示した自動ダンプ比較をサポートしています。Google によると、ダンプ比較は蓄積効果により、単一分析では見えないリークを発見できます。
実際のプロジェクトでのヒープダンプ分析に基づいて、実証済みのメモリ最適化プラクティスが開発されました。WeakReference を使用する:キャッシュ、コールバック、長寿命オブジェクトのコンテキスト参照に。リスナーを登録解除する:Android では onPause/onDestroy、iOS では deinit で。大きな静的コレクションを避ける:必要な場合は、サイズ制限付きの LruCache を使用します。Bitmap を最適化する:適切な inSampleSize で画像を読み込み、ディスクキャッシュ付きの Glide または Picasso を使用します。
CI パイプラインに定期的なヒープダンプキャプチャを含めます。インストルメント化された UI テストを実行し、主要なユーザーシナリオを実行し、ヒープダンプをベースラインと比較するタスクを設定します。retained size がベースラインから5%以上増加した場合、ビルドはリグレッションとしてマークされます。このアプローチは、Airbnb、Uber、および高品質基準を持つ他の企業で実践されています。Uber Engineering によると、CI での自動ヒープダンプ分析の実装により、メモリ関連のバグが四半期で70%削減されました。
// CI で自動ヒープダンプを実行する Gradle タスクの例
task profileMemory(type: Exec) {
commandLine 'adb', 'shell',
'am start -n com.example/.MainActivity'
// 読み込み待機中
doLast {
exec { commandLine 'adb', 'shell',
'am broadcast -a com.example.DUMP_HEAP' }
}
}
よくある質問
Shallow size はオブジェクト自体のサイズ(フィールド + ヘッダー)です。Retained size は、オブジェクトと、それが削除された場合にガベージになるすべてのオブジェクトのサイズです。Retained size は、メモリ消費に対するオブジェクトの影響を示す主要な指標です。
Android Studio Profiler で、デバイスとプロセスを選択し、Dump Java Heap をクリックします。または、コマンドライン経由:adb shell am dumpheap PID /sdcard/dump.hprof、その後 adb pull。
ヒープダンプにはすべての ライブ オブジェクトが含まれます。アプリケーションがキャッシュ、Bitmap、または大規模データを処理する場合、ダンプは数百メガバイトに達する可能性があります。クラスでフィルタリングするか、Eclipse MAT を使用してインデックスのみをロードします。
はい、Eclipse MAT(Memory Analyzer Tool)を使用してください — .hprof ファイルを分析するための無料ツール。Dominator tree、path to GC roots、ダンプ比較、Leak Suspects Report による自動リーク検出をサポートしています。
ダンプ自体 — はい、ダンプ収集はすべてのスレッドを停止するため(stop-the-world)。ダンプなし — いいえ。制御された環境(テストベンチ、CI)でダンプを取得し、本番環境では取得しないでください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。