メモリ管理について

メモリの最適化は、Android で安定した高性能なゲーム エクスペリエンスを提供するために不可欠です。このガイドでは、メモリ効率が重要な理由、Android オペレーティング システムがプロセス メモリの上限を管理して適用する方法、Google Play Console に表示される新しいしきい値について説明します。これらのしきい値は、ゲームの技術的な品質をモニタリングして改善するのに役立ちます。

メモリの最適化の重要性

ゲームのメモリを最適化することは、プレーヤーの維持、デバイスの互換性の拡大、プラットフォームの品質基準の遵守に不可欠です。

  • コールド スタートの防止(ユーザー エクスペリエンスと維持): プレーヤーがゲームから一時的に離れる(通知に応答したり、メッセージを確認したりするなど)と、オペレーティング システムはゲーム プロセスをバックグラウンドに配置します。ゲームのバックグラウンド メモリ使用量が大きすぎると、システムのローメモリ キラー(LMK)は、フォアグラウンド タスク用に RAM を再利用するために、ゲーム プロセスの強制終了を優先します。ユーザーが次に再開するときは、シームレスで瞬時のウォーム再開ではなく、ゲームは長いコールド スタートを行う必要があります。つまり、重いグラフィック アセット、音声、ゲームエンジンのバイナリをストレージから完全に再読み込みします。バックグラウンドでのメモリ使用量を抑えることで、バックグラウンドでのサイレントな強制終了を防ぎ、ユーザーの状態を維持し、プレーヤーがセッションをすぐに再開できるようにします。システムの LMK の動作について詳しくは、Android Vitals - ローメモリ キラーのガイドをご覧ください。
  • エコシステムとデバイスの安定性: メモリ使用量の非効率性とメモリ リークは、システム全体の健全性を低下させます。システムメモリが不足すると、システムに大きな負荷がかかり、フレームレートの低下、UI のカクつき、音声の不具合が発生します。メモリ プレッシャーが大きすぎると、システムのローメモリ キラー(LMK)がバックグラウンド プロセスを強制終了し、プレーヤーがタスクを切り替えるときに他のアプリケーションでコールド スタートが遅くなったり、ユーザーの状態が失われたりします。
  • プラットフォーム レベルでの強制終了: Android 17(API レベル 37)以降では、 メモリを過剰に使用するプロセスをより積極的に 強制終了します。ゲームのフットプリントが大きすぎると、OS は標準のスタック トレースを生成せずにプロセスを突然強制終了することがあります。
  • デバイスの互換性: フラッグシップ デバイスには 12 GB ~ 16 GB の RAM が搭載されていますが、世界のゲーム ユーザーの大部分は 4 GB または 6 GB の RAM を搭載したデバイスを使用しています。適切なメモリ管理により、複雑で別個のアセット パッケージを必要とせずに、すべてのハードウェア層でゲームにアクセスして応答できるようにします。

Android のメモリについて

効果的なメモリ バジェット戦略を設計するには、デベロッパーは Android プラットフォームが物理メモリを管理する方法と、ゲームのアクティブ フットプリントを測定する方法を理解する必要があります。

Android のメモリに関する基本コンセプト

プラットフォーム レベルのメモリ管理に関する基本コンセプトについては、 公式の メモリ管理の概要ドキュメントをご覧ください。このリソースでは、次の 4 つのアーキテクチャ領域について説明します。

  • メモリの概要: Android は、ページングとメモリ マッピング(mmap)を使用して RAM を管理します。ディスク上の従来のスワップ ファイルはサポートされていません。代わりに、ページ圧縮(zRAM を使用)とページ再利用に依存して、物理メモリを解放します。
  • プロセス間のメモリ割り当て: Android は、システム全体で RAM を共有します。Dalvik または ART 仮想マシンの実行に特定のヒープを割り当てますが、ネイティブ開発環境(C++ ゲームエンジンなど)はネイティブ システムヒープからメモリをリクエストできます。
  • アプリのメモリ管理: マルチプロセス モデルで動作する Android では、アプリケーションがライフサイクル状態を動的にモニタリングし、 システムの状態を維持するために不要なリソース(キャッシュされていないグラフィックや ビットマップなど)を自発的に解放することが想定されています。
  • プロセスとスレッドの概要: システムは、現在のユーザーが認識できる可視性と重要度に基づいて、プロセスを 階層に分類します。これにより、メモリ不足のときにどのプロセスを維持し、どのプロセスを最初に強制終了するかを 判断します。

合計メモリ使用量指標

プラットフォーム レベルの Android 17 メモリ リミッターは、合計常駐サイズ(RSS)または仮想メモリ サイズではなく、合計メモリ フットプリントを使用してプロセスの消費量を評価します。

合計メモリ フットプリント = 匿名 RSS(RssAnon)+ 非圧縮スワップ(VmSwap)

ゲームがプラットフォームで適用される上限を超えないようにするには、デベロッパーはこれらの指標がシステム レベルで何を表しているかを正確に理解する必要があります。これらの指標、物理 RAM の割り当て、ファイルバックアップ ページの処理方法について詳しくは、RSS 指標とスワップ指標についてメモリ 使用量をモニタリングするガイドでご覧ください。

メモリの制約

システムの安定性を維持し、アプリケーションが過剰なリソースを消費しないようにするため、Android プラットフォームは実行中のプロセスにメモリ上限を適用します。

Android 17 以降のメモリ リミッター

Android 17 以降では、Linux cgroup v2 を使用してアプリごとに厳格なメモリ上限が適用され、個々のアプリがシステム全体の不安定さを引き起こすのを防ぎます。技術的な実装について詳しくは、AOSP メモリ リミッター ガイドメモリ効率の優先: Android 17 の重要な手順のブログをご覧ください。

  • メカニズム: メモリ リミッターはすべてのアプリケーション プロセスをモニタリングし、 プロセスのライフサイクル状態に基づいて上限を動的に割り当てます:
    • 表示プロセス(フォアグラウンド): 現在 UI を表示しているアプリ プロセスは、より大きなリソース ワーキング セットを実行することが想定されており、より 寛大な上限が与えられます。
    • 非表示プロセス(バックグラウンドまたはサービス): UI を表示せずに アクティブな作業を行うアプリ プロセスは、より厳格な予算に制限されます。
  • カーネル属性: このサービスは、次の 2 つの主要な属性に依存しています:
    • memory.high: ソフト上限。この上限を超えると、カーネルはプロセスをスロットリングし、メモリの積極的な再利用を試みます。この再利用により、ゲームのパフォーマンスが低下する可能性があります。
    • memory.swap.max: プロセスが使用できるスワップまたは zRAM スペースにハードキャップを適用します。
  • 強制終了の動作: プロセスが匿名 メモリを memory.high を超えて割り当て続け、スワップ容量を使い果たすと、割り当ては失敗し、 OS はプロセスを強制終了します。この強制終了は、 ApplicationExitInfo を使用して、メモリ リミッター の終了理由 (Android 17、26Q4 以降で利用可能)の下に記録されます。

メモリ使用量をモニタリングする

ゲームのメモリを効果的に最適化するには、まず Android プラットフォームがフットプリントを測定する方法を理解する必要があります。Android 17 では、メモリ適用指標が更新され、ファイルバックアップ メモリまたは GPU プライベート メモリを除き、匿名 RSS(RssAnon)と非圧縮スワップ(VmSwap)の合計を追跡します。このガイドでは、Perfetto や meminfo などのシステムレベルのツールを活用し、ProfilingManageronTrimMemory などの診断 API を実装して、Unity と Unreal Engine 内で正確なメモリ割り当てを抽出する方法について詳しく説明します。ゲームを正確にプロファイリングし、従来のランタイム メモリ ポーリングに関連するパフォーマンスの低下を回避する方法を理解してください。

詳しくは、メモリ使用量をモニタリングするをご覧ください。

メモリ削減戦略

ゲームエンジンはクロスプラットフォーム開発を簡素化しますが、デフォルトのメモリ処理では OS レベルのメモリ上限がトリガーされる可能性があります。このページでは、Unity と Unreal Engine に合わせて調整された実践的な最適化手順について詳しく説明します。Java ベースの onTrimMemory に依存すると Unity でデッドロックが発生する可能性がある理由と、代わりにネイティブ ライフサイクル コールバックを使用する方法について説明します。また、ASTC 8x8 テクスチャ圧縮の使用やアセットのアンロードの構成など、アセットレベルの重要な最適化についても説明します。これにより、すべてのハードウェア層でゲームをスムーズに実行できます。