この記事は Unity Advent Calendar 2025 の10日目の記事です。
経緯
Android 実機で突然アプリクラッシュした経験があったので、その振り返りとしてまとめています。
メモリリークに近い内容ですが、C#の参照が残っているためGC(ガベージコレクション)で回収されないメモリについての話になります。
環境
- Unity 6000.0.31f1
- Memory Profiler 1.1.9
古いUnityバージョンでも手順はおおむね同じです。
前準備
PackageManagerからMemoryProfilerをインストールします。
インストールできていればProfilerを開いて、Memoryタブを選択すると、Open Memory Profilerが有効になります。
調査のしかた
メモリの蓄積を調べるには、ある時点からある時点のメモリアロケーションのスナップショットの差分を取り、アロケーションが増えていることを確認します。
手順は以下になります。
- メモリが蓄積される部分をProfilerで計測
- ある時点のスナップショットAを撮影
- ある時点のスナップショットBを撮影
- スナップショットA・Bを比較
- アロケーション箇所を特定
今回のサンプルコード
using UnityEngine;
using System.Collections.Generic;
public class Sample : MonoBehaviour
{
private readonly List<byte[]> _leakList = new List<byte[]>();
void Update()
{
byte[] data = new byte[1024 * 1024]; // 1MB
// コンパイラのストリップを防ぐためのダミー処理
data[0] = (byte)(Time.frameCount % 255);
// 参照を保持してGCに乗せない
_leakList.Add(data);
// 現在の確保済みサイズを表示
Debug.Log($"[Leaker] Allocated: {_leakList.Count} MB");
}
}
まず Profiler で計測して、蓄積前にシークを動かし、MemoryProfiler を開きます。
Memory Profilerのウィンドウが表示されます。
左上のカメラマークのCapture、中央のCapture New Snapshotを押すとスナップショットが撮れます。
「メモリに見られたらまずいデータがある場合は共有に気を付けてね」と警告が出る場合は、理解したうえでTake Snapshotを押してください。
しばらく待つとスナップショットが完了します。
Profilerに戻り、蓄積されているフレームを選択して、再びMemory ProfilerのCaptureを押してください。
左のSessionの欄にスナップショットが並んでいきます。
Sessionに並んでいるスナップショットのプロジェクト名を選択すると、名前が編集できます。
スナップショットは上から下に新しいものが並びますが、回数が増えると、どれが対となるデータか分からなくなってくるので、適宜名前を付けていきましょう。
上のSingle SnapShotをCompare Snapshotsに切り替えます。
2つNo snapshot selected.と表示されており、これは比較前(上)、比較後(下)となります。
この状態でSession欄の比較前のスナップショットを選択します。
そして、比較後のスナップショットを選択します。
ⒶとⒷのしるしが付いていれば、正しく選択できています。
Ⓐが比較前、Ⓑが比較後のスナップショットの対象とされます。
選択されたスナップショットのTotal Resident等で見て、選択間違えが起きてないかチェックしましょう。
この状態になれば、スナップショットの差分から原因を特定するフェーズになります。
分析
上部にタブでアロケーションの種類別にみることができます。
-
Summary- 全体的に差分が確認できます
- 最初に調査のアタリ付けたい場合はここを見ます
-
Unity Objects- Unity オブジェクトのアロケーションの一覧です
- リークのマーキングや参照元がわかるため、メモリリークした場合はすぐに発見できます
-
All Of Memory- 全てのアロケーションの一覧です
- アタリが付けにくい場合は、ここを見て細かく調査できます
今回はAll Of Memoryタブに切り替えて調査します。
中央のリストはどういうメモリが確保されているかの詳細です。
メモリの区分けはざっくり以下の通りです。
-
Managed- C#側のメモリ
-
Native- C++側のメモリ
-
Executables & Mapped- プロセスレベルのメモリ
-
Graphics(Estimated)- グラフィックスドライバとGPUのメモリ
-
Untracked*- 上記で網羅できなかったメモリ
Unityスクリプトの場合は、Native、Managedの2つが調査対象の中心になります。
Count Differenceがアロケーションの回数差分、Size Differenceがアロケーションのメモリサイズ差分です。
Size Differenceが大きいManagedの中のManaged Objectsを見ると、どのような型でアロケーションされたかわかります。
アプリケーションの状態から心当たりがあるアロケーション探っていきます。
心当たりのあるものを見つけたら開き、一つアロケーションを選択します。
すると、右側に参照情報が表示されます。
Referenced By(参照元)とReferences To(参照先)のタブがあります。
ここで重要なのが、Referenced Byの情報です。
このツリーを下に辿っていくことで、大元の参照保持のオブジェクトが見つかります。
あとはコードを確認し、以下のような点を見直して修正を行います。
- イベントの購読寿命を間違えてないか
-
Clear()やDispose()を呼び忘れてないか -
TaskやUniTaskのキャンセル処理を正しく行われているか
以上が調査する流れになります。
まとめ
Memory Profilerはグラフを多くウッとなりますが、見方がわかると簡単にメモリアロケーションを調査することができます。
不可解なアロケーションをなくしてUXを上げていきましょう。
参考文献












