ガベージコレクション
※この章を読むにはスタックメモリとヒープメモリの理解が必要です。
背景
古いプログラム言語では変数を宣言して確保したメモリを自分で解放する必要がありました。概念的には以下のように書く必要がありました。
static void Main(string[] args)
{
    Person p1 = new Person("Higty", 24);
    //何かの処理...
    ReleaseMemory(p1);
}
しかしプログラムの規模が大きくなるとメモリの解放のコードを書き忘れたりすることがよくありました。 書き忘れるとメモリリークが発生し最悪の場合プログラムが停止してしまいます。 また解放したメモリにアクセスした場合もプログラムが停止してしまいます。 ガベージコレクションとは不要になったメモリを自動で解放するための仕組みです。 この仕組みがあることでプログラマはメモリ管理のことを考えなくてよくなり、 自分が作りたいプログラムそのもののコーディングに専念することが可能になります。
不要なメモリがどうやってできるのか?
以下のようなコードを考えて見ます。
static void Main(string[] args)
{
    Person p1 = new Person("Higty", 24);
}
このときメモリの様子は以下のようになります。
ここで注目してほしいのがHigtyが入っているメモリの値です。ここの値はどこからも参照されていません。 さらに以下のコードを実行します。
static void Main(string[] args)
{
    Person p1 = new Person("Higty", 24);
    String s1 = "Messi";
    Person p2 = new Person("Tiger", 36);
    p2 = new Person("Blot", 26);
}
同様のことをひたすら繰り返していくとメモリは以下のようなイメージになります。
上図でどこからも参照されていないヒープメモリ上のメモリブロックのデータをクリアし再度利用できるようにするための仕組みがガベージコレクションです。
ガベージコレクションの動作の概要
準備作業
ガベージコレクションが始まるとCLRはアプリケーションで実行中の全てのスレッドを停止させます。 これはガベージコレクションによってメモリの参照先を変更している途中で実行中のスレッドが変数宣言などを行うとメモリの位置が不正になるためです。
到達可能フラグの設定
全てのスレッドが停止されるとルーツから到達可能なメモリブロックを記録する処理が開始されます。 ルーツとはヒープメモリ上のアドレスを記憶するものでヒープメモリのオブジェクトかnullのどちらかを参照しています。 ルーツとなるのは以下の3つになります。
■グローバル変数・静的変数
■スレッドスタック上のローカル変数とメソッド引数の変数
■CPUレジスタ上の変数
上図で到達不可能なものにをつけると以下のようになります。
既にリストにある場合はそこで探索を終了します。これによって参照が循環している場合でも正しく全てのリストを構築することが可能です。
不要なメモリのクリア&メモリの移動
リストの作成が完了すると不要になったメモリをクリアし空いたスペースにメモリを移動させます。この作業をメモリのコンパクションと言います。
移動が完了すると停止したスレッドの実行を再開させアプリケーションは通常の状態に戻ります。
パフォーマンス
解説からも推測できるようにガベージコレクションは非常に重たい処理です。
■スレッドの停止
■到達可能フラグの設定
■メモリのクリア、メモリの移動
これらの処理が非常に時間がかかります。またガベージコレクションの実行中はアプリケーションのスレッドが停止しているのでUIは応答しなくなります。 なるべくガベージコレクションが発生しないようメモリを無駄使いしないコードを書くのが望ましいです。
世代管理
ガベージコレクションは重たい処理ですがその処理速度を少しでも早くするためにガベージコレクションには世代管理という概念があります。 ここでは世代管理について解説していきます。
メモリの使用傾向
メモリの使用傾向の各種の実験や検証などから以下の事実が明らかになっています。

1.オブジェクトが新しいほどその寿命は短い
2.オブジェクトが古いほどその寿命は長い
3.新しいオブジェクトは互いに強い関係を持ち、同時に頻繁にアクセスされる
4.ヒープの一部のコンパクションはヒープ全体のコンパクションよりも高速である

これらの事実は自分が実際に作成したプログラムを改めてみてみると確かにそうであることが多いと感じると思います。 1、2に関してはローカル変数、メンバー変数、静的変数の順に寿命が短いことは感覚的に理解できると思います。 3に関してはメソッド内のローカル変数同士は何かしら関連がある場合が多くこれも感覚的に理解できると思います。 メモリの使用傾向を踏まえてガベージコレクションには世代という概念が導入されています。 世代管理を行うことで結果として高速なガベージコレクションが実現可能になっています。
世代とは?
オブジェクトが宣言されメモリが確保されるとそのオブジェクトは世代0に所属しています。 ガベージコレクションが発生しそれでも生き残ったメモリは世代が1つ増え世代1に所属することになります。 .NET4.5までの各バージョンでは世代の最大値は2です。世代2に属するオブジェクトがガベージコレクションのあと生き残った場合は世代は2のままです。 メモリが足りなくなるとガベージコレクションはまず最初に世代0のオブジェクトに対してだけ処理を行います。 それでもまだ足りない場合は世代1、まだ足りない場合は世代2に対して処理を行います。
高速化の理由
世代管理を導入することで1と2と4の傾向をうまく生かして到達不能なオブジェクトを特定することができます。 世代が2のオブジェクトは古くからいるオブジェクトとなり世代が0のオブジェクトは比較的新しいオブジェクトということになります。 世代が2のオブジェクトは寿命が長い可能性が高くガベージコレクションを行ってもあまりメモリの回収ができないことが多いです。 逆に世代が0のオブジェクトは寿命が短い可能性が高いので多くのメモリを回収できます。
新しいオブジェクトを割り当てるときに今まで使用したメモリの末尾に連続して追加していくことで3の強い関係を生かすことができます。 この割り当て方であれば同じ時期に宣言したオブジェクトはメモリ上で非常に近い位置に配置されることになります。 これによって関係のあるオブジェクト同士がCPUのキャッシュにまとめてロードされる可能性が高くなりキャッシュミスが減ることが期待されます。
.NET4.5でのガベージコレクションの改善点
http://blogs.msdn.com/b/dotnet/archive/2012/07/20/the-net-framework-4-5-includes-new-garbage-collector-enhancements-for-client-and-server-apps.aspx
Create at 2012/8/17 LastUpdate 2012/8/28