Version: Unity 6.0 (6000.0)
言語 : 日本語
Unity のメモリ
Managed memory introduction

マネージメモリ

Unity のマネージメモリシステムは、Mono または IL2CPP 仮想マシン (VM) をベースにした C# スクリプト環境です。マネージメモリシステムの利点は、メモリの解放を管理してくれることです。そのため、コードを通してメモリの解放を手動でリクエストする必要はありません。

Unity のマネージメモリシステムは、スクリプトが割り当てられたメモリへの参照を維持しなくなると、ガベージコレクターマネージヒープ を使用して、自動的にメモリ割り当てを解放します。これにより、メモリリークを防止することができます。メモリリークは、メモリが割り当てられ、その参照が失われ、その後、そのメモリが解放されない場合に発生します。なぜなら、そのメモリを解放するには、そのメモリへの 参照 が必要だからです。

このメモリ管理システムは、メモリアクセスも監視します。つまり、解放されたメモリや、コードがアクセスするために有効ではなかったメモリにはアクセスできないということです。ただし、マネージメモリの割り当ては CPU にとって時間がかかるため、このメモリ管理プロセスはランタイムのパフォーマンスに影響を与えます。また、ガベージコレクション が終了するまで、CPU は他の作業をしない場合もあります。

値型と参照型

メソッドが呼び出されると、スクリプティングバックエンドはそのパラメーターの値を、コールスタックと呼ばれるデータ構造内のその特定の呼び出しのために予約されたメモリ領域にコピーします。スクリプティングバックエンドは、数バイトのデータタイプを素早くコピーできます。しかし、オブジェクト、文字列、配列などのデータはずっと大きくなることが多く、スクリプティングバックエンドがこれらの型のデータを定期的にコピーするのは非効率です。

マネージコード内の null でない全ての 参照型オブジェクト および全ての ボックス化された値型オブジェクト は、マネージヒープに割り当てられる必要があります。

値型と参照型に精通していることは重要です。そうすれば、コードを効果的に管理することができます。詳細については、Microsoft のドキュメント 値型参照型 を参照してください。

自動メモリ管理

オブジェクトが作成されると、Unity はそのオブジェクトを格納するのに必要なメモリをヒープと呼ばれる主要プールから割り当てます。ヒープは、Unity プロジェクトで選択されたスクリプティングランタイム (Mono または IL2CPP) が自動的に管理するメモリのセクションです。オブジェクトが使用されなくなると、そのオブジェクトが使用していたメモリは再利用され、別の用途に使用されます。

Unity のスクリプティングバックエンド は、ガベージコレクター を使用して、アプリケーションのメモリを自動的に管理します。そのため、明示的なメソッド呼び出しでメモリブロックを割り当てたり解放したりする必要がなくなります。自動メモリ管理は、明示的な割り当て/解放よりもコーディングの手間が少なく、メモリリークの可能性も低減します。

マネージヒープの概要

マネージヒープは、Unity プロジェクトで選択されたスクリプティングランタイム (Mono または IL2CPP) が自動的に管理するメモリのセクションです。

メモリの量。図の A は空きメモリを示します。
メモリの量。図の A は空きメモリを示します。

上の図で、青いボックスはマネージヒープに割り当てられたメモリ量を示しています。中にある白いボックスが、マネージヒープのメモリスペース内に格納されたデータの値を示しています。追加のデータ値が必要になると、マネージヒープの使用されていないスペース (A) が割り当てられます。

メモリの断片化とヒープの拡張

メモリの量。解放された一部のオブジェクトがグレーの破線で表示されています。
メモリの量。解放された一部のオブジェクトがグレーの破線で表示されています。

上の図はメモリの断片化の一例を示しています。Unity がオブジェクトを解放すると、オブジェクトが占有していたメモリは解放されます。しかし、その空きスペースは、1 つの大きな “空きメモリ” プールの一部にはなりません。

解放されたオブジェクトに隣り合うオブジェクトは、まだ使用されている可能性があります。このため、解放されたスペースは、メモリの他のセグメントの間の “ギャップ” になります。このギャップは、解放されたオブジェクトと同じかそれ以下のサイズのデータを格納することにしか利用できません。

このような状況をメモリの断片化と呼びます。これは、ヒープに大量のメモリがあるにもかかわらず、オブジェクトとオブジェクトの間の “ギャップ” でしか利用できない場合に起こります。つまり、大きなメモリ割り当てのために十分な総スペースがあるにもかかわらず、マネージヒープには、それを割り当てられるのに十分な大きさの、ひとまとまりの連続したメモリブロックが見つからないのです。

A と記されたオブジェクトは、ヒープに追加する必要がある新しいオブジェクトです。B の項目は、解放されたオブジェクトが占めていたメモリスペースと、予約されていない空きのメモリです。空きスペースの合計が十分にあるにもかかわらず、連続したスペースが十分にないため、A の新しいオブジェクトのためのメモリはヒープに収まらず、ガベージコレクターを実行する必要があります。
A と記されたオブジェクトは、ヒープに追加する必要がある新しいオブジェクトです。B の項目は、解放されたオブジェクトが占めていたメモリスペースと、予約されていない空きのメモリです。空きスペースの合計が十分にあるにもかかわらず、連続したスペースが十分にないため、A の新しいオブジェクトのためのメモリはヒープに収まらず、ガベージコレクターを実行する必要があります。

上の図のように、大きなオブジェクトが割り当てられ、それを格納するためのひとまとまりの空き領域が不足している場合、Unity のメモリマネージャは 2 つの処理を行います。

  • まず、まだガベージコレクターが実行されていなければ、ガベージコレクターが実行されます。これにより、割り当てリクエストを満たすのに十分なスペースを確保しようとします。
  • ガベージコレクターの実行後、リクエストされた量のメモリを収めるのに十分な連続したスペースがない場合、ヒープを拡張する必要があります。ヒープが拡張される具体的な量はプラットフォームに依存しますが、ほとんどのプラットフォームでは、ヒープが拡張される場合、前回の拡張の 2 倍の量だけ拡張されます。

マネージヒープの拡張に関する検討事項

ヒープの予期せぬ拡大は問題となる可能性があります。Unity のガベージコレクションによる方法は、より頻繁にメモリを断片化する傾向があります。以下の点に注意する必要があります。

  • Unity では、マネージヒープが定期的に拡張される際、そのマネージヒープに割り当てられたメモリが解放されることはありません。拡張したヒープの大部分が空であったとしても Unity は拡張したヒープを維持し続けます。これは、さらに大きな割り当てが行われた場合に、ヒープを再拡張しなくて済むようにするためです。
  • ほとんどのプラットフォームでは、Unity はある時点で、マネージヒープの空部分に使用されているメモリを解放し、OS に返します。これが行われる間隔は不確実で一定ではありません。
  • ガベージコレクターは、ネイティブメモリオブジェクトやその他のネイティブ割り当てを消去しません。Resources.UnloadUnusedAssets は、それを指す参照がなくなった全てのネイティブオブジェクトに対してこれを行います。また、これらの参照の状態が最新であることを確認するための GC.Collect もトリガーされます。以下の点に留意してください。
    • UnloadUnusedAssets は自動的にはトリガーされず、手動での呼び出し時とシーンの変更時にのみトリガーされます。そのメモリを早めに解放したい場合、例えば、RAM が少ないプラットフォームの全画面 RenderTexture のために解放が不可欠な場合、メモリを最適に使用するには、オブジェクトに対して Destroy を呼び出す必要があります。
    • マネージメモリリークのリスクがあります。クリーンアップされたオブジェクトへの参照を維持している場合、マネージオブジェクトが参照するネイティブオブジェクトがリークする可能性があります。静的なフィールドとイベントは、この種のメモリリークの一般的な原因です。Memory Profiler でこれらの問題を分析する方法の詳細については、Memory Profiler パッケージのドキュメントの マネージシェルオブジェクト を参照してください。
    • UnloadUnusedAssets または GC.Collect を呼び出すと、ゲームプレイ中に回避した方がよい CPU 負荷の高いプロセスがトリガーされます。
Unity のメモリ
Managed memory introduction