アプリ開発におけるOutOfMemoryError:その原因と防止策

著者: IT Sectr 公開日: 2026-03-29 読了時間: 9 分

OutOfMemoryErrorは、Java仮想マシン(JVM)またはAndroid Runtime(ART)がヒープ(Heap)の容量不足により新しいオブジェクトにメモリを割り当てられない場合に発生する致命的な例外です。Square Engineeringによると、モバイルアプリケーションにおけるOutOfMemoryErrorの70%は、実際の制限超過ではなくメモリリークが原因です。OOMの原因を理解することが、アプリケーションの安定性の鍵です。

重要なポイント

  • OutOfMemoryError — 新しいオブジェクトを作成するためのHeapが不十分な場合の例外
  • Heap — すべてのJava/Kotlinオブジェクトが存在するメモリ領域
  • Bitmap — AndroidにおけるHeapの主要な消費者、典型的なOOMの原因
  • Heap Dump — 誰がどれだけメモリを占有しているかを分析するためのHeapのスナップショット
  • OOMの対策 にはリークの修正とメモリ消費の最適化が必要

OutOfMemoryErrorとは

OutOfMemoryError(OOM)は、Java/KotlinのVirtualMachineErrorファミリーに属する例外で、新しいオブジェクトにメモリを割り当てられないことを示します。チェック例外とは異なり、OOMはErrorでありcatchによる処理は必要ありませんが、技術的にキャッチすることは可能です。OOMが発生した後、アプリケーションは通常不安定な状態になり、終了することが推奨されます。

Androidでは、各アプリケーションにデバイスメーカーが設定したHeap制限があります。6+ GB RAMの最新スマートフォンの場合、制限は256~512 MB、低価格デバイスでは128~192 MBです。すべての生存オブジェクトの総量がこの制限を超えると、ARTがOutOfMemoryErrorをスローします。

重要なのは、OOMは必ずしもデバイスの物理メモリが不足したことを意味しないということです。これは、アプリケーションがシステムによって設定されたHeap制限を使い果たしたことを意味します。他のアプリケーションには空きメモリがあるかもしれませんが、Androidのプロセス分離のため、あなたのアプリケーションはそれを使用できません。

OutOfMemoryErrorの主な原因

5つのシナリオが定期的にモバイルアプリケーションでOOMを引き起こします。各シナリオは特定のデータ型または操作に関連しています。

スケーリングなしのBitmap

BitmapはAndroidアプリケーションにおける主要なメモリ消費者です。FullHD画像(1920×1080)を元のサイズで読み込むと、ARGB_8888形式で8.3 MBを占有します。RecyclerViewにそのような画像が50枚ある場合、415 MBになり、どのデバイスのHeapも超えます。inSampleSizeなしでの画像読み込みは、低スペックデバイスでのOOMを確実に引き起こします。

自動スケーリングにはGlideまたはCoilを使用してください。これらのライブラリは、元の解像度ではなくViewに合わせたサイズで画像を読み込みます。BitmapFactory.Optionsを直接使用する場合は、inSampleSizeを適用します。2の累乗として計算し、最終的なサイズが2048×2048ピクセルを超えないようにします。さらに、透明度のない画像にはARGB_8888の代わりにRGB_565を使用すると、メモリ消費が半分になります。

kotlin
fun loadScaledBitmap(path: String, reqWidth: Int): Bitmap? {
    val opts = BitmapFactory.Options().apply {
        inJustDecodeBounds = true
    }
    BitmapFactory.decodeFile(path, opts)
    opts.inSampleSize = calculateSampleSize(opts.outWidth, reqWidth)
    opts.inJustDecodeBounds = false
    return BitmapFactory.decodeFile(path, opts)
}

メモリリーク(蓄積)

1つの数KBのリークはOOMを引き起こしません。しかし、各画面での数十のリークが蓄積されます。画面遷移ごとにリークが追加され、GCがオブジェクトを解放できず、Heapが満杯になります。典型的なパターン:ユーザーがプロフィール画面を20回開閉する→Heapが200 MB増加する→アプリケーションがOOMでクラッシュする。

プロジェクトにLeakCanaryをインストールして、リークを自動検出してください。正確なスタックトレースとともに、リークした各オブジェクトを表示します。すべてのリークを修正した後、Heap消費は安定します。画面を閉じた後、メモリはベースラインレベルに戻ります。

メモリ内の大容量ファイル

ファイル全体をbyte[]に読み込むことは、OOMへの直接的な道です。50 MBのJSONファイルは、解析中に同じサイズの文字列とDOMモデルを作成します。メモリに読み込まれた動画ファイル、オーディオバッファ、大規模なprotobufデータセットは、すべて1回の操作でHeap制限を超える可能性があります。

大規模データはストリームで処理してください。4~8 KBバッファのInputStream、ストリーミングJSONパーサー(JacksonまたはGson + JsonReader)、動画にはMediaCodec。利用可能なHeapの10%を超えるファイルでFile.readBytes()を呼び出さないでください。

ループ内での大量オブジェクト生成

ループ内で中間GCなしにオブジェクトを大量に生成すると、特にHeapの小さいデバイスでOOMを引き起こす可能性があります。例:GCが収集できる前にHeapに収まらない100,000個のオブジェクトをfor-loopで生成する。これはゲームやグラフィックエディタでより一般的です。

大量に作成・破棄されるオブジェクトにはObject Poolを使用してください。数値データにはプリミティブを使用します(List<Float>の代わりにFloatArray)。ViewHolder Poolを備えたRecyclerViewは、UIコンポーネントでこの問題を解決します。

Heapの断片化

断片化とは、合計では十分な空きメモリがあるが、新しいオブジェクト用の連続したブロックがない状態です。ARTはGC中にHeapを圧縮しますが、常に成功するとは限りません。大きな配列(Bitmap、byte[])は断片化の影響を最も受けやすくなります。

Android 8+のARTは世代別GCを使用しており、若いオブジェクトと古いオブジェクトを分離することで断片化を低減します。それでも、同じプール内で異なるサイズの断片を割り当てることは避け、事前に割り当てられた固定サイズのバッファを使用するようにしてください。

AndroidのHeap制限

AndroidのHeap制限は定数ではありません。メーカー、デバイスモデル、OSバージョンに依存します。GoogleはCompatibility Definition Document(CDD)を通じて最小要件を定めていますが、実際の値はメーカーが設定します。

デバイスカテゴリ標準HeaplargeHeap
低価格帯(1~2 GB RAM)128~192 MB256~384 MB
ミッドレンジ(3~4 GB RAM)256~384 MB512 MB
フラッグシップ(6+ GB RAM)384~512 MB768 MB~1 GB
タブレット(4+ GB RAM)256~512 MB768 MB
Wear OS32~64 MBなし

マニフェストでandroid:largeHeap="true"を使用して、拡張された制限をリクエストできます。注意して使用してください。Heapを増やしてもリークの問題は解決されず、システムがあなたのアプリケーションのためにメモリを解放するために他のアプリケーションを強制終了する必要がある場合、ユーザー体験が悪化する可能性があります。Wear OSの場合、Heap制限は最小の32~64 MBのみで、largeHeapは利用できず、メモリ節約は二重に重要です。

OutOfMemoryErrorの診断

OOMの診断には、Heap Dumpの分析と、どのオブジェクトがメモリを消費しているかの理解が必要です。Android Studioは必要なツールをすべて提供します。

ステップ1:OOMの瞬間をキャプチャします。Android Memory Profilerで、Record memory allocationsをクリックし、クラッシュを引き起こすシナリオを実行します。ProfilerはOOMの前に割り当ての急増を示します。OOMが再現できない場合は、デバッグビルドでandroid:smallHeapを使用してHeapを減らすか、手動GC呼び出しでDDMSを使用します。

ステップ2:ピーク負荷時(OOMの前)にHeap Dumpを取得します。Android StudioでDumpを開きます。ClassesタブはRetained Sizeでソートされています。最大のオブジェクトはBitmap、byte[]、Stringです。各Bitmapについて、サイズ(幅×高さ×4バイト)とStack Traceによる読み込みパスを確認します。

ステップ3:重複オブジェクトの数を分析します。200個の同一のFragmentまたはActivityが見られる場合、それはリークです。同じサイズの500個のBitmapがある場合、画像キャッシュの問題です。MAT(Memory Analyzer Tool)は、Dominator Treeを使用して、どのオブジェクトがHeapの80%を保持しているかを示すより深い分析を提供します。

text
// adb経由のHeap Dumpコマンド
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.

OOM防止戦略

包括的なOOM防止戦略には、アーキテクチャ上の決定から本番環境での監視まで、5つのレベルの保護が含まれます。

アーキテクチャ上の決定

ViewModel + Repositoryパターンは、データをUIから分離し、画面回転時のViewの保持を防ぎます。ViewModelはActivityよりも長生きし、そのデータは失われず、Viewはメモリ内のデータを複製せずに再作成できます。明示的な状態管理にはLiveDataの代わりにStateFlowを使用してください。

Bitmapと画像の管理

Glideは画像を扱うための必須ライブラリです。自動的にスケーリング、キャッシュ(ディスク+メモリ)、Bitmapのリサイクルを行います。大規模なリストの場合は、diskCacheStrategyとskipMemoryCacheを設定してください。アニメーション画像には、GIF/WebPとともにGlideを使用します。これらはBitmapのシーケンスよりも少ないメモリしか消費しません。

本番環境での監視

Firebase Performance Monitoringは、メモリ消費をリアルタイムで追跡します。Heap使用率が制限の80%を超えた場合にアラートを設定してください。これは調査のシグナルです。CrashlyticsはOOMを例外として収集し、クラッシュ前の最終既知のHeap状態を表示します。Android 11+では、ApplicationExitInfoを使用してOOM終了を検出します。

低スペックデバイスでのテスト

必ず最小Heap(128~192 MB)のデバイスでアプリケーションをテストしてください。小さな画面と小さなHeapのエミュレータは低価格デバイスをエミュレートします。そのようなデバイスでアプリケーションが動作すれば、フラッグシップでOOMの問題は発生しません。さまざまな価格帯の実デバイスでFirebase Test Labを使用してください。

kotlin
// 高負荷操作前の利用可能Heapの確認
fun canAllocate(requiredBytes: Long): Boolean {
    val runtime = Runtime.getRuntime()
    val free = runtime.freeMemory()
    return free > requiredBytes * 2 // 50%バッファ
}

よくある質問

OutOfMemoryErrorはtry-catchでキャッチできますか?

技術的には可能ですが、推奨されません。OOM後、アプリケーションは不安定な状態にあり、新しい割り当てが失敗したり、一部のオブジェクトが部分的に作成されたりする可能性があります。catchで合理的な唯一のアクションは、ロギングとActivityの再起動です。

なぜOOMはすべてのデバイスで発生しないのですか?

Heap制限はデバイスによって異なります。300 MBを必要とする操作は、192 MBの制限があるデバイスではクラッシュしますが、512 MBのフラッグシップでは成功します。OOMシナリオを検出するには、最小スペックのデバイスでテストしてください。

largeHeapはパフォーマンスにどのような影響を与えますか?

largeHeapは制限を増やしますが、アプリケーションを高速化しません。大きなHeapの収集にはより時間がかかるため、GC一時停止が長くなります。システムはメモリを確保するためにバックグラウンドアプリケーションを強制終了する可能性があります。largeHeapは、客観的に多くのメモリを必要とするアプリケーション(カメラ、エディタ)にのみ使用してください。

OOMとシステムキルの違いは何ですか?

OOMはHeapが不十分な場合のアプリケーション内部の例外です。システムキル(Low Memory Killer)は、他のアプリケーションにメモリを解放するためにプロセスを強制終了するLinuxカーネルの決定です。システムキルの場合、アプリケーションは例外を受け取らず、プロセスは単に終了します。

Bitmapは実際にどのくらいのメモリを消費しますか?

計算式:幅×高さ×bytesPerPixel。ARGB_8888 = 4 B/ピクセル、RGB_565 = 2 B/ピクセル。ARGB_8888のFullHD Bitmap(1920×1080) = 8.3 MB。4K Bitmap(3840×2160) = 33 MB。画像は常に画面表示に必要なサイズにスケーリングしてください。

まとめ

  • OutOfMemoryError — アプリケーションのHeap制限が枯渇した場合の致命的な例外
  • スケーリングなしのBitmap — モバイルアプリケーションにおけるOOMの主な原因
  • メモリリークは、遷移ごとのオブジェクト蓄積によりOOMの70%を引き起こす
  • Heap制限は低価格デバイスで128 MBからフラッグシップで512 MBまで
  • Heap DumpとRetained Size分析がOOM診断の主要ツール
  • GlideまたはCoilは、あらゆるサイズの画像を扱うために必須
  • 最小Heapのデバイスでのテストはすべてのプロジェクトに必須

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください