Unityでメモリリークを分析するための方法をまとめました。
- Profilerでメモリが増え続けていることを確認する
- Profilerでネイティブメモリに乗っているオブジェクト数をチェックする
- ProfilerのMemoryをDetailedモードにして参照関係をチェックする
- Memory Profilerでメモリをキャプチャして明らかに数の多いオブジェクトを探す
- Memory ProfilerのDiff機能でフレーム間の差分を取る
- 関連
Unity2018.4.0f1
Profilerでメモリが増え続けていることを確認する
まずは何はともあれメモリがリークしていることをProfilerできちんと確認します。
Profilerの基本的な使い方は次の記事にまとめていますので必要に応じて参照してください。
メモリのProfilerはネイティブメモリとマネージドメモリのそれぞれについて、
使用している容量と予約(確保)している容量を表示することができます。
要はこれらがひたすらに増え続けていればどこかでリークしていることになります。
ただし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を使ってオブジェクト数をチェックする方法があります。
赤枠部分にロードされているオブジェクトの数が表示されているので、これが増え続けたらリークしていることになります。
例えば、下記の記事のようにしてマテリアルがリークするとMaterialsの数がひたすら増えていきます。
上記マテリアルのようにこの段階で原因に検討が付く場合にはこの時点で修正を入れます。
ProfilerのMemoryをDetailedモードにして参照関係をチェックする
あるオブジェクトがどのオブジェクトから参照されているかを知りたい場合にはProfilerのMemoryをDetailedモードにします。
具体的には以下の手順で参照情報を取得します。
- Detailedモードに変更
- 参照関係を見たいタイミングでTake Sample Editorをクリック
- 参照関係を見たいオブジェクトをクリック
- 右側に参照関係がツリー構造で表示される
ちなみに参照情報にManagedStaticReferences()がある場合には、そのオブジェクトがどこかしらのStaticな領域から参照されていることを示します。
(これがあると分析が大変・・)
このモードはメモリリークを追うのに便利そうですが、個人的にはいまいち使いこなせていません。
こんな機能もある、程度に留めておくのがいいかもしれません。
Memory Profilerでメモリをキャプチャして明らかに数の多いオブジェクトを探す
より詳細にメモリを分析したい場合にはMemory Profilerを使います。
基本的な使い方に関しては別の記事としてまとめたので必要に応じて参照してください。
メモリリークを分析するには怪しい部分の処理をしばらく走らせた後にキャプチャします。
そして画面下部のFilters:と書かれているウィンドウで以下のようにカラムの設定を行います。
- Data TypeをGroup表示にする
- TypeをGroup表示にする
- Owned SizeをSort Descendingにする
すると以下のような見た目になります。
Data TypeカラムのNative Objectはネイティブメモリを、Managed Objectはマネージドメモリを示すので主にこの二つを見ます。
試しにManaged Objectを展開してみます。
するとマネージドメモリに割り当てられているオブジェクトが大きい順にずらっと表示されます。
これを上から見ていき、明らかに数がおかしいものが無いか確認します。
その結果、上図のようにEnemyクラスが1000個生成されていることが発覚しました。
このゲームでは一度にメモリに乗る敵は最大10体と決まっているのでこれはメモリリークであると考えられます。
また、二つのフレームのキャプチャを上から見比べていくと、明らかにおかしいオブジェクトが見つかりやすいです。
(片方のフレーム情報の画面キャプチャを取っておいて、他方のMemoryProfiler画面と見比べる感じです)
なお怪しいオブジェクトを見つけたらRefCountをクリックすれば参照をたどることができます。
Memory ProfilerのDiff機能でフレーム間の差分を取る
Memory ProfilerのDiff機能を使うと、二つのフレームのメモリを比較することもできます。
手順としては、まずゲームを再生して一回キャプチャを取ります。
そしてメモリリークしてそうな部分を繰り返し処理します(例えばガチャを引き続けるとか)。
その後、もう一度キャプチャを取り、これら二つのキャプチャのDiffモードにします。
Diffモードにしたら以下の操作をしておきます。
- DiffをGroup表示にする
- TypeをGroup表示にする
- Owned SizeをSort Descendingにする
すると次のような画面になります。
次にDiffのNewグループを展開します。
するとデータ型が、メモリに占める容量が大きい順に表示されます。
今回の例ではTestという型が異様に多く生成されていることがわかりました。
この方法も有用ではありますが、個人的には前節のように二つのフレームのキャプチャを目視で比べていくほうが確実だし、
結果的に問題点が見つかるまでの時間も短いかなと思っています。