Version: 2022.1
言語: 日本語
Unity のメモリ
メモリアロケーターのカスタマイズ

マネージメモリ

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

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

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

値型と参照型

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

マネージコード内のすべての null ではない 参照型オブジェクト とすべての ボックス化された値型オブジェクト は、マネージヒープ上に割り当てられなければなりません。

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

自動メモリ管理

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

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

マネージヒープの概要

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Unity では、マネージヒープが定期的に拡張される際、そのマネージヒープに割り当てられたメモリが解放されることはありません。拡張したヒープの大部分が空であったとしても Unity は拡張したヒープを維持し続けます。これは、さらに大きな割り当てが行われた場合に、ヒープを再拡張しなくて済むようにするためです。
  • ほとんどのプラットフォームでは、Unity はある時点で、マネージヒープの空部分に使用されているメモリを解放し、OS に返します。これが行われる間隔は不確実で一定ではありません。
Unity のメモリ
メモリアロケーターのカスタマイズ