メモリリークと肥大化 — その原因と回避方法

著者: IT Sectr 公開日: 2026-07-28 読了時間: 10 分

メモリリーク — モバイル開発において最も厄介な問題の一つです。アプリのメモリ使用量はOSが設定した制限に達するまで着実に増加し、その後OutOfMemoryErrorまたは強制終了が発生します。Square Engineeringによると、約40%のAndroidアプリに少なくとも1つのメモリリークが存在し、プロファイリングによってのみ検出可能です。ここでは原因とメモリ増大の防止策を解説します。

重要ポイント

  • GC到達可能性 — ルートセットからのアクティブな参照がある場合、オブジェクトは削除されない
  • ActivityやContextへの静的参照 — Androidにおけるリークの最も一般的な原因
  • LeakCanary — Androidでの自動リーク検出の標準ツール
  • WeakReference — ガベージコレクションを妨げるべきでない参照の解決策
  • Lifecycle-awareコンポーネント — ビュー破棄時に自動的にサブスクリプションをキャンセル

メモリリークとアプリの肥大化とは?

メモリリーク — アプリに不要になったオブジェクトが、GCルートセットからのアクティブな参照がまだ存在するためにヒープに保持され続ける状態。ガベージコレクタはそのようなオブジェクトを生存していると見なし、削除しません。

メモリ肥大化 — アプリが現在のタスクを実行するために必要な以上にメモリを消費する、より広範な問題。原因:過剰なキャッシュ、オブジェクトの重複、非効率なデータ構造、ヒープの断片化。

Androidでは、各アプリに制限付きのヒープが割り当てられます(通常、デバイスとOSバージョンに応じて64–512MB)。iOSでは制限はそれほど厳しくありませんが、上限に近づくとシステムがメモリ警告を送信します。

特性AndroidiOS
ヒープ制限64–512MB(デバイスによる)暗黙的(システム)
ガベージコレクションART(同時、コンパクト)ARC(自動参照カウント)
リークの仕組みGCルート参照リテインサイクル(強参照サイクル)
結果OutOfMemoryErrorメモリ警告 → 終了

Facebook Engineering Blogによると、メモリリークはモバイルアプリのクラッシュレポートの約15%を占めています。Androidでは、メモリ不足時の頻繁なGCポーズによるANRも加わります。

AndroidとiOSにおける一般的なメモリリークパターン

Activityへの静的参照 — 古典的なAndroidリーク。静的フィールドやシングルトンがActivityへの参照を保持している場合、シングルトンが生存している限り、finish()後もGCによって収集されません。Activityはビュー階層、リソース、Contextを含む重いオブジェクトです。

kotlin
object LeakHolder {
    var activityRef: Activity ?= null // leak: static reference to Activity
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        LeakHolder.activityRef = this // ❌ MainActivity will never be GC'd
    }
}

匿名クラスとラムダ — 外部クラスへの参照を暗黙的に保持します。RunnableやCallbackが外部サービスに渡され、Activityが破棄された場合でも、匿名クラスのオブジェクトはキューに残り、Activityのガベージコレクションを妨げます。

  • Handlerと遅延 — Activityが破棄されたがHandler.postDelayedがまだ実行されていない場合、Activityがリークする
  • ThreadとAsyncTask — 画面回転時にActivityは再作成されるが、古いThreadは古いActivityへの参照を保持し続ける
  • Retrofit/Callback — 匿名Callbackがプレゼンターやフラグメントへの参照を保持する
  • オブザーバー — onDestroyで購読解除しないLiveDataやRxJavaのサブスクリプション

iOSでは、主な問題はリテインサイクルです:2つのオブジェクトが互いに強参照を保持し、ARCがいずれの参照カウントもゼロにできません。典型的なケース:selfを強くキャプチャするクロージャと、クロージャへの参照を保持するself。

メモリリークを検出する方法

LeakCanary — Androidでの自動リーク検出のためのSquareのライブラリ。ActivityやFragmentが破棄された後、オブジェクトがGCによって収集されたかどうかを確認します。収集されていない場合、ヒープダンプを取得し、リークトレースを表示します。

kotlin
// LeakCanary 2.x — auto-integration via Application
class ExampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // LeakCanary auto-installs in debug build
        // via ContentProvider — zero code setup
    }
}

// Force check invocation
AppWatcher.objectWatcher.watch(watchedObject, "leak description")

Android Studio Profiler — リアルタイムメモリ監視のための組み込みツール。ヒープダンプの記録、疑わしいオブジェクト(Retained Size > 1MB)の発見、各オブジェクトへのGCルートパスの追跡が可能です。

iOSにはXcode Memory Graph Debuggerを使用します。メモリ内のオブジェクトグラフを可視化し、リテインサイクルを表示し、循環参照を即座に検出できます。Instruments > Allocationsも長期監視に利用可能です。

防止戦略

WeakReference — ガベージコレクションを妨げるべきでない参照のための基本メカニズム。GCがオブジェクトを収集することを決定した場合、WeakReferenceはnullを返します。コールバック、リスナー、バックグラウンドスレッドからのUIコンポーネントへの参照に使用されます。

Lifecycle-awareコンポーネント — Android Jetpack(Lifecycle、LiveData、Flow、coroutines)で実装されたアーキテクチャアプローチ。サブスクリプションはonDestroyで自動的にキャンセルされ、主要なリーククラスを排除します。

kotlin
class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<List<User>>()
    val data: LiveData<List<User>> get() = _data

    fun loadData() {
        viewModelScope.launch {
            val result = repository.fetchData()
            _data.postValue(result)
            // coroutine auto-cancels on onCleared()
        }
    }
}

viewModelScopelifecycleScope — Androidに組み込まれたCoroutineScopeで、対応するライフサイクルイベントでキャンセルされます。これにより、現代のAndroid開発で最も一般的なシナリオであるコルーチンを介したリークを排除します。

  • 使用しない:Context、Activity、View、Fragmentへの静的参照
  • キャンセルする:onDestroyでdisposeBag / CompositeDisposable内のすべてのRxJavaサブスクリプション
  • 使用する:iOSクロージャで[weak self] / [unowned self]によりリテインサイクルを防止
  • 確認する:Bitmapや大きなオブジェクトはリサイクルまたはnull化する必要がある

メモリプロファイリングツール

Android StudioのMemory Profiler — ヒープ監視のための主要ツール。ライブアロケーション、ヒープスナップショット、タイプ別のオブジェクト数を表示します。ダンプを記録し、MAT(Memory Analyzer Tool)で分析して疑わしいオブジェクトを見つけることができます。

Eclipse MAT — デスクトップヒープダンプアナライザ。Android StudioからHPROFファイルをロード後、MATは支配ツリーを構築し、各オブジェクトのretainサイズを表示し、Leak Suspects Reportを通じて自動リーク分析を提供します。

Xcode Memory Graph — ビジュアルリテインサイクルデバッガ。Memory Graph Debuggerボタンをクリックすると、Xcodeはアプリを停止し、メモリ内の完全なオブジェクトグラフを構築し、リテインサイクルを赤色で強調表示します。

ツールプラットフォーム特徴
LeakCanaryAndroid破棄後のリーク自動検出
Memory ProfilerAndroid Studioヒープダンプ + ライブアロケーション
Eclipse MATAndroid支配ツリー、Leak Suspects Report
Memory GraphiOS(Xcode)リテインサイクル可視化

Google I/O 2023によると、デバッグビルドでLeakCanaryを使用するアプリは、導入後最初の2ヶ月でメモリ関連のクラッシュが30–50%減少します。プロジェクトのオンボーディング段階でLeakCanaryを追加することを推奨します。

よくある質問

メモリリークと肥大化の違いは何ですか?

リーク — コードからアクセスできないが、アクティブな参照のためにGCによって収集されないオブジェクト。肥大化 — アプリが論理的には必要だが過剰な量のオブジェクトを保持する状態(例:80MBの実行中アプリで50MBのキャッシュ)。肥大化はアーキテクチャ的に解決し、リークは正しい参照管理によって解決します。

LeakCanaryはどのようにリークを見つけますか?

LeakCanaryはObjectWatcherを使用します — ActivityのonDestroy()後、ActivityへのWeakReferenceを作成しGCを実行します。5秒経ってもWeakReferenceがクリアされない場合、ヒープダンプを取得し、GC Rootからオブジェクトへの最短参照チェーンを分析し、ファイルとコード行を含む正確なリークスタックを表示します。

Bitmapが頻繁にOutOfMemoryErrorを引き起こす理由は?

BitmapはJavaヒープ外のネイティブメモリ(ネイティブヒープ)を占有します。1つのBitmapのサイズ = 幅 × 高さ × 4バイト(ARGB_8888)。12MPの写真(4000×3000)は48MBを占有します。Androidは常にタイムリーにネイティブメモリを解放できるとは限らず、複数のBitmapが蓄積すると、十分なJavaヒープがあってもOOMが発生します。

iOSのリテインサイクルとは何ですか?

リテインサイクル — ARCにおいて、2つのオブジェクトが互いに強参照を保持し、参照カウントが決してゼロにならない状態。典型的な例:クロージャへの強参照を持つViewControllerと、selfを強くキャプチャするクロージャ。解決策:クロージャで[weak self]または[unowned self]を使用します。

Androidの最大ヒープサイズは?

ヒープサイズはデバイスとAndroidバージョンによって異なります。古いデバイス(API 15–24)は64–128MB、現代のデバイス(API 25+)は256–512MBです。正確な値はActivityManager.getMemoryClass()で取得できます。大きなアプリ(ゲーム、エディタ)の場合、マニフェストでlargeHeap=trueを設定すると最大1GBまで提供されます。

まとめ

  • メモリリーク — ルートセットからのアクティブな参照によりGCが収集できないオブジェクト;肥大化 — 明示的なリークなしでの過剰なメモリ消費
  • Activity、Context、Viewへの静的参照 — Androidにおけるリークの第1の原因;解決策 — WeakReferenceまたはApplication Context
  • 匿名クラスとラムダは外部クラスへの参照を暗黙的に保持;キャンセルされていないコールバックが2番目に多い原因
  • LeakCanary — Androidでのリーク自動検出の標準;統合に5分かかり、クラッシュ率を30–50%削減
  • lifecycleScopeviewModelScopeは破棄時に自動的にコルーチンをキャンセルし、リークのクラス全体を排除
  • iOSのリテインサイクルはクロージャとデリゲートでweak/unowned selfを使用して解決
  • メモリをプロファイリングする — スプリントに少なくとも1回、MATまたはMemory Graphでのヒープダンプをコードレビューの一部にすべき

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

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

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

こちらもお読みください