Automatic Memory Management

自動メモリ管理は、マネージド実行中に共通言語ランタイムが提供するサービスの 1 つです。 共通言語ランタイムのガベージ コレクターは、アプリケーションが使用するメモリの割り当ておよび解放を管理します。 したがって、開発者がマネージド アプリケーションを開発するときに、メモリ管理タスクを実行するためのコードを記述する必要はありません。 自動メモリ管理により、オブジェクトを解放し忘れたためにメモリ リークが発生する、既に解放されているオブジェクトのメモリにアクセスしようとするなどの一般的な問題を回避できます。 ここでは、ガベージ コレクターによるメモリの割り当て方法および解放方法について説明します。

メモリの割り当て

新しいプロセスが初期化されると、ランタイムは連続したアドレス空間領域をそのプロセスのために予約します。 この予約済みのアドレス空間をマネージド ヒープと呼びます。 マネージド ヒープは、ヒープ内で次のオブジェクトを割り当てるアドレスへのポインターを管理します。 初期状態では、このポインターはマネージド ヒープのベース アドレスに設定されます。 すべての参照型は、マネージド ヒープ上に割り当てられます。 アプリケーションが最初の参照型を作成すると、マネージド ヒープのベース アドレスの位置にその型のメモリが割り当てられます。 アプリケーションが次のオブジェクトを作成すると、ガベージ コレクターは、アドレス空間で最初のオブジェクトの直後のメモリをそのオブジェクトに割り当てます。 ガベージ コレクターは、使用できるアドレス空間がある限り、この方法で新しいオブジェクトにアドレス空間を割り当てていきます。

マネージド ヒープからのメモリ割り当ては、アンマネージド メモリ割り当てよりも高速に処理されます。 ランタイムはポインターに値を加算することによってオブジェクトにメモリを割り当てるため、この方法によるメモリ割り当ては、スタックからのメモリ割り当てとほとんど同じ速度で行われます。 また、連続してメモリを割り当てられた複数の新規オブジェクトは、マネージド ヒープ内でも連続して格納されるため、アプリケーションからそれらのオブジェクトに高速でアクセスできます。

メモリの解放

ガベージ コレクターの最適化エンジンは、現在の割り当て状況に基づいて、ガベージ コレクションの実行に最適な時期を判断します。 ガベージ コレクターは、ガベージ コレクションを実行するときに、アプリケーションが使用しなくなったオブジェクトのメモリを解放します。 使用されなくなったオブジェクトを判断するために、アプリケーションのルートを調べます。 すべてのアプリケーションには、ルートのセットがあります。 各ルートは、マネージド ヒープ上のオブジェクトを参照しているか、または null に設定されています。 アプリケーションのルートには、静的フィールド、スレッドのスタック上のローカル変数とパラメーター、CPU レジスタなどが含まれています。 ガベージ コレクターは、ジャスト イン タイム (JIT) コンパイラとランタイムが管理している、アクティブなルートのリストにアクセスします。 このリストを使用してアプリケーションのルートを調べ、ルートから到達できるすべてのオブジェクトを含むグラフを作成します。

このグラフに含まれないオブジェクトは、アプリケーションのルートからは到達できません。 ガベージ コレクターは、そうした到達できないオブジェクトをガベージ (ごみ) であると判断し、それらのオブジェクトに割り当てられたメモリを解放します。 ガベージ コレクション中に、ガベージ コレクターはマネージド ヒープを調べ、到達できないオブジェクトが占有しているアドレス空間ブロックを検索します。 到達できないオブジェクトを検出すると、それらのオブジェクトに割り当てられていたアドレス空間ブロックを解放し、メモリ コピー機能を使用して、到達できるオブジェクトのメモリを圧縮します。 到達できるオブジェクトのメモリを圧縮した後で、ガベージ コレクターは、アプリケーションのルートがそれらのオブジェクトの新しい位置を指すようにポインターを修正します。 また、マネージド ヒープのポインターも、最後の到達できるオブジェクトの後を指すように修正します。 メモリが圧縮されるのは、ガベージ コレクション中に、到達できないオブジェクトが一定数以上検出された場合だけです。 マネージド ヒープ内のすべてのオブジェクトがごみではないと判断された場合は、メモリを圧縮する必要がありません。

パフォーマンスを向上させるために、ランタイムは、大きいオブジェクトのメモリは独立したヒープに割り当てます。 ガベージ コレクターは、これらの大きいオブジェクトのメモリを自動的に解放します。 ただし、メモリ内で大きいオブジェクトを移動するのを避けるため、このメモリは圧縮されません。

ジェネレーションとパフォーマンス

ガベージ コレクターのパフォーマンスを最適化するために、マネージド ヒープは 0、1、および 2 の 3 つのジェネレーションに分割されます。 ランタイムのガベージ コレクション アルゴリズムは、これまでのガベージ コレクション手法の利用経験から、コンピューター ソフトウェア業界で有効だと認識されている次の原則に基づいています。 まず、マネージド ヒープの一部のメモリを圧縮する方が、マネージド ヒープ全体のメモリを圧縮するよりも高速です。 次に、オブジェクトが新しいほどその存続期間は短く、古いほど存続期間は長くなります。 最後に、新しいオブジェクトは相互に関連を持つ傾向にあり、アプリケーションによって同時に頻繁にアクセスされます。

ランタイムのガベージ コレクターは、新しいオブジェクトをジェネレーション 0 に格納します。 アプリケーションの有効期間の初期に作成され、ガベージ コレクションでごみではないと判断されたオブジェクトは昇格してジェネレーション 1 とジェネレーション 2 に格納されます。 オブジェクトの昇格プロセスについては後で説明します。 マネージド ヒープの一部を圧縮する方がヒープ全体を圧縮するよりも高速であるため、この手法では、ガベージ コレクターがコレクションを実行するたびにマネージド ヒープ全体のメモリを解放するのではなく、特定のジェネレーションのメモリだけを解放できるようにします。

実際には、ガベージ コレクターはジェネレーション 0 がいっぱいになったときにガベージ コレクションを実行します。 ジェネレーション 0 がいっぱいになったときにアプリケーションが新しいオブジェクトを作成しようとすると、ガベージ コレクターは、そのオブジェクトに割り当てるためのアドレス空間がジェネレーション 0 には残っていないことを認識します。 ガベージ コレクターは、ジェネレーション 0 のアドレス空間を解放して新しいオブジェクトに割り当てるために、ガベージ コレクションを実行します。 まず、ガベージ コレクターは、マネージド ヒープ内の全オブジェクトではなく、ジェネレーション 0 のオブジェクトだけを調べます。 一般に新しいオブジェクトの存続期間は短く、ジェネレーション 0 のオブジェクトの多くはガベージ コレクションが実行された時点でアプリケーションによって使用されていないと推測されるため、この方法は最も効率的です。 また、多くの場合、ジェネレーション 0 のガベージ コレクションを行うだけで、アプリケーションが新しいオブジェクトの作成を続行するために十分なメモリを確保できます。

ガベージ コレクターは、ジェネレーション 0 のガベージ コレクションを実行した後で、このトピックの「メモリの解放」で既に説明したように、到達できるオブジェクトのメモリを圧縮します。 次に、ガベージ コレクターはこれらのオブジェクトを昇格させ、それらのオブジェクトが占めているマネージド ヒープ内の部分をジェネレーション 1 と見なします。 一般にガベージ コレクションでごみだと判断されなかったオブジェクトの存続期間は長いので、これらのオブジェクトを上位のジェネレーションに昇格させるのは有効です。 この結果、ガベージ コレクターがジェネレーション 0 のガベージ コレクションを実行するたびに、ジェネレーション 1 とジェネレーション 2 に昇格したオブジェクトを再び調べる必要がなくなります。

ジェネレーション 0 の最初のガベージ コレクションを実行し、到達できるオブジェクトをジェネレーション 1 に昇格させた後で、ガベージ コレクターはマネージド ヒープの残りの部分をジェネレーション 0 と見なします。 ガベージ コレクターは、ジェネレーション 0 がいっぱいになってガベージ コレクションを再び実行する必要が生じるまで、ジェネレーション 0 のメモリを新しいオブジェクトに割り当てます。 この時点で、ガベージ コレクターの最適化エンジンは、古いジェネレーションのオブジェクトを調べる必要があるかどうかを判断します。 たとえば、ジェネレーション 0 のガベージ コレクションを行うだけでは、アプリケーションが新しいオブジェクトを作成するために必要なメモリを確保できない場合、ガベージ コレクターはジェネレーション 1、0 の順にガベージ コレクションを実行します。 これでも必要なメモリを確保できない場合は、ジェネレーション 2、1、および 0 の順にガベージ コレクションを実行します。 ガベージ コレクターは、それぞれのガベージ コレクション後にジェネレーション 0 内にある到達できるオブジェクトを圧縮し、それらのオブジェクトをジェネレーション 1 に昇格させます。 ガベージ コレクションでごみだと判断されなかったジェネレーション 1 のオブジェクトは、ジェネレーション 2 に昇格します。 ガベージ コレクターがサポートするジェネレーションは 3 つしかないため、ガベージ コレクションでごみだと判断されなかったジェネレーション 2 のオブジェクトは、その後のガベージ コレクションで到達できないオブジェクトであると判断されるまで、ジェネレーション 2 に残ります。

アンマネージ リソースのメモリの解放

アプリケーションで作成されるオブジェクトの大部分については、ガベージ コレクターによって、必要なメモリ管理タスクを自動的に実行できます。 しかし、アンマネージ リソースでは、明示的なクリーンアップが必要です。 最も一般的な種類のアンマネージ リソースは、ファイル ハンドル、ウィンドウ ハンドル、ネットワーク接続などのオペレーティング システム リソースをラップしたオブジェクトです。 ガベージ コレクターは、アンマネージド リソースをカプセル化したマネージド オブジェクトの存続期間を追跡することはできますが、そのアンマネージド リソースのクリーンアップ方法については具体的な情報を持っていません。 アンマネージ リソースをカプセル化するオブジェクトを作成する場合は、そのアンマネージ リソースをクリーンアップするために必要なコードをパブリックな Dispose メソッドという形で提供することをお勧めします。 Dispose メソッドを提供すると、ユーザーがオブジェクトを使い終わったときに、そのオブジェクトのメモリを明示的に解放できます。 アンマネージ リソースをカプセル化するオブジェクトを使用する場合は、Dispose メソッドの存在を念頭に置き、必要に応じて呼び出すようにしてください。 アンマネージ リソースのクリーンアップの詳細と、Dispose を実装するためのデザイン パターンの例については、「ガベージ コレクション」を参照してください。

関連項目