【Unity】Unityでメモリリークを分析するための5つの方法

Unityでメモリリークを分析するための方法をまとめました。

Unity2018.4.0f1

Profilerでメモリが増え続けていることを確認する

まずは何はともあれメモリがリークしていることをProfilerできちんと確認します。
Profilerの基本的な使い方は次の記事にまとめていますので必要に応じて参照してください。

light11.hatenadiary.com

メモリのProfilerはネイティブメモリとマネージドメモリのそれぞれについて、
使用している容量と予約(確保)している容量を表示することができます。

f:id:halya_11:20190825212357p:plain

要はこれらがひたすらに増え続けていればどこかでリークしていることになります。
ただしUnityエディタで再生する場合にはエディタ用のアセットなども乗ってきてしまって正常に計測できないので、
この数値を計測する場合はビルド後のバイナリとProfilerを接続することをお勧めします。

また、これらの値はスクリプトから取得することもできます。

using UnityEngine;
using UnityEngine.Profiling;

public class Example : MonoBehaviour
{
    private void Update()
    {
        var monoUsedSize = Profiler.GetMonoUsedSizeLong() / 1024f / 1024f;
        var monoReservedSize = Profiler.GetMonoHeapSizeLong() / 1024f / 1024f;
        var unityUsedSize = Profiler.GetTotalAllocatedMemoryLong() / 1024f / 1024f;
        var unityReservedSize = Profiler.GetTotalReservedMemoryLong() / 1024f / 1024f;
        
        Debug.Log(nameof(monoUsedSize) + $" : {monoUsedSize}MB");
        Debug.Log(nameof(monoReservedSize) + $" : {monoReservedSize}MB");
        Debug.Log(nameof(unityUsedSize) + $" : {unityUsedSize}MB");
        Debug.Log(nameof(unityReservedSize) + $" : {unityReservedSize}MB");
    }
}

Profilerでネイティブメモリに乗っているオブジェクト数をチェックする

GameObjectやアセットなどのネイティブメモリがリークしている場合、
お手軽な方法としてProfilerを使ってオブジェクト数をチェックする方法があります。

f:id:halya_11:20190825213252p:plain

赤枠部分にロードされているオブジェクトの数が表示されているので、これが増え続けたらリークしていることになります。
例えば、下記の記事のようにしてマテリアルがリークするとMaterialsの数がひたすら増えていきます。

light11.hatenadiary.com

上記マテリアルのようにこの段階で原因に検討が付く場合にはこの時点で修正を入れます。

ProfilerのMemoryをDetailedモードにして参照関係をチェックする

あるオブジェクトがどのオブジェクトから参照されているかを知りたい場合にはProfilerのMemoryをDetailedモードにします。
具体的には以下の手順で参照情報を取得します。

  1. Detailedモードに変更
  2. 参照関係を見たいタイミングでTake Sample Editorをクリック
  3. 参照関係を見たいオブジェクトをクリック
  4. 右側に参照関係がツリー構造で表示される

f:id:halya_11:20190816133015p:plain

ちなみに参照情報にManagedStaticReferences()がある場合には、そのオブジェクトがどこかしらのStaticな領域から参照されていることを示します。
(これがあると分析が大変・・)

このモードはメモリリークを追うのに便利そうですが、個人的にはいまいち使いこなせていません。
こんな機能もある、程度に留めておくのがいいかもしれません。

Memory Profilerでメモリをキャプチャして明らかに数の多いオブジェクトを探す

より詳細にメモリを分析したい場合にはMemory Profilerを使います。
基本的な使い方に関しては別の記事としてまとめたので必要に応じて参照してください。

light11.hatenadiary.com

メモリリークを分析するには怪しい部分の処理をしばらく走らせた後にキャプチャします。
そして画面下部のFilters:と書かれているウィンドウで以下のようにカラムの設定を行います。

  1. Data TypeをGroup表示にする
  2. TypeをGroup表示にする
  3. Owned SizeをSort Descendingにする

すると以下のような見た目になります。
Data TypeカラムのNative Objectはネイティブメモリを、Managed Objectはマネージドメモリを示すので主にこの二つを見ます。
試しにManaged Objectを展開してみます。

するとマネージドメモリに割り当てられているオブジェクトが大きい順にずらっと表示されます。
これを上から見ていき、明らかに数がおかしいものが無いか確認します。

f:id:halya_11:20190825215501p:plain

その結果、上図のようにEnemyクラスが1000個生成されていることが発覚しました。
このゲームでは一度にメモリに乗る敵は最大10体と決まっているのでこれはメモリリークであると考えられます。

また、二つのフレームのキャプチャを上から見比べていくと、明らかにおかしいオブジェクトが見つかりやすいです。
(片方のフレーム情報の画面キャプチャを取っておいて、他方のMemoryProfiler画面と見比べる感じです)

なお怪しいオブジェクトを見つけたらRefCountをクリックすれば参照をたどることができます。

f:id:halya_11:20190825220605p:plain

Memory ProfilerのDiff機能でフレーム間の差分を取る

Memory ProfilerのDiff機能を使うと、二つのフレームのメモリを比較することもできます。

手順としては、まずゲームを再生して一回キャプチャを取ります。
そしてメモリリークしてそうな部分を繰り返し処理します(例えばガチャを引き続けるとか)。 その後、もう一度キャプチャを取り、これら二つのキャプチャのDiffモードにします。

Diffモードにしたら以下の操作をしておきます。

  1. DiffをGroup表示にする
  2. TypeをGroup表示にする
  3. Owned SizeをSort Descendingにする

すると次のような画面になります。

f:id:halya_11:20190822102852p:plain

次にDiffのNewグループを展開します。
するとデータ型が、メモリに占める容量が大きい順に表示されます。

f:id:halya_11:20190822103217p:plain

今回の例ではTestという型が異様に多く生成されていることがわかりました。

この方法も有用ではありますが、個人的には前節のように二つのフレームのキャプチャを目視で比べていくほうが確実だし、
結果的に問題点が見つかるまでの時間も短いかなと思っています。

関連

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com