21 章のお話
オブジェクトヘッダ 型オブジェクトポインター (4byte, 8byte) 型の構造体へのポンタ 同期ブロックインデックス (4byte, 8byte) ロックとか COM で利用する フィールド
マネージヒープ NextObjPtr
マネージヒープ NextObjPtr オブジェクト A を割り当てたい! 同期ブロック 同期ブロックインデックス ~ フィールドまでが入るようにする 型ハンドル フィールド
マネージヒープ A NextObjPtr オブジェクト A を割り当てたら NextObjPtrj がオブジェクトの直後まで進む
マネージヒープ A B NextObjPtr
マネージヒープ A B C NextObjPtr オブジェクトの割り当ては単なるポインタの加算なので非常に速い
マネージヒープ A B C D E F G H オブジェクトを割り当てようとしたが十分なアドレス空間がのこっていない! NextObjPtr ガベージコレクション実行!
全てのスレッドを一時停止 ガベージコレクション終わるまですべてのスレッドがオブジェクトにアクセスできない GC マーキングフェイズ 使ってないオブジェクトを探す コンパクションフェイズ 不要なオブジェクトを削除して圧縮する
ルート : フィールドや変数 マネージヒープ A B C D E F G H 参照を表す クラスの静的またはインスタンスフィールド メソッドの実引数 ローカル変数などで参照型の変数を総称してルートと呼びます NextObjPtr
ルート : フィールドや変数 マネージヒープ A B C D E F G H 0 0 0 0 0 0 0 0 全オブジェクトの同期ブロックインデックスフィールドに含まれているビットに 0 を指定 これはすべてのオブジェクトを削除するという意味 NextObjPtr
ルート : フィールドや変数 マネージヒープ A B C D E F G H 1 0 1 1 0 1 0 0 ルートから直接参照されているオブジェクトをマークします NextObjPtr
ルート : フィールドや変数 マネージヒープ A B C D E F G H 1 0 1 1 0 1 1 0 マークをする際に もし他のオブジェクトを NextObjPtr 参照している場合は そのオブジェクトもマークします この例だとDをマークするときはGもマークします
ルート : フィールドや変数 マネージヒープ A B C D E F G H 1 0 1 1 0 1 1 0 これでマーキングフェイズが完了です マークされたオブジェクトは到達可能といいます マークされていないのは到達不能といいます NextObjPtr
ルート : フィールドや変数 マネージヒープ A B C D E F G H 1 0 1 1 0 1 1 0 マークされているオブジェクトによって使用されているメモリを移動させてメモリ上に連続するようにします NextObjPtr
ルート : フィールドや変数 A C C D F G マネージヒープ オブジェクトを移動させる際に オブジェクトを参照しているルートなどは移動した分のバイト数を引く必要があります
ルート : フィールドや変数 マネージヒープ A C D F G マークされているすべてのオブジェクトに対して行います
メモリの空容量を連続的にすることで マネージヒープ上でメモリの断片化がなくなります
OutOfMemoryException の例外が発生します アプリケーションはその例外をキャッチして回復を試みることができますが ほとんどのアプリケーションはやってないのでプロセスが終了して OS がプロセスが使用していたメモリを解放します
メソッド内のオブジェクトの生存期間は最後に参照したところまでです メソッドの終了時までじゃ ないです
static void Main(string[] args) { Timer t = new Timer(TimerCallback, null, 0, 2000); } // タイミング A Console.ReadLine(); // オブジェクト t の参照 Console.WriteLine(t.ToString()); // タイミング B Console.ReadLine();
static void Main(string[] args) { Timer t = new Timer(TimerCallback, null, 0, 2000); // タイミング A Console.ReadLine(); } // オブジェクト t の参照 Console.WriteLine(t.ToString()); // タイミング B Console.ReadLine(); オブジェクト t は保障される オブジェクト t は保障されないガベージコレクションが実行されたら消える
なお Debug でビルドした場合 JIT コンパイラが生存期間を恣意的にメソッドの最後まで伸ばします Release ビルドと Debug ビルドで動きがかわるぞ! がっでむ!
CLR の GC は世代別ガレージコレクタを採用している 世代別 GC は次のことを前提にしている オブジェクトが新しいほど その生存期間は短い オブジェクトが古いほど その生存期間は長い ヒープの一部分の回収はヒープ全体の回収より高速である
マネージヒープ A B C D E 世代 0 新しく追加されるオブジェクトは常に世代 0 に追加される
マネージヒープ A B C D E 世代 0 しばらくしてオブジェクト C と E が到達不能となるその後 ガベージコレクションが発生したとする
マネージヒープ A B D 世代 1 世代 0 ガベージコレクションの後に世代 0 で生き残ったオブジェクトが世代 1 に移動して世代 0 は空になる
マネージヒープ A B D F G H 世代 1 世代 0 新しいオブジェクトは世代 0 に割り当てられていく 世代 0 の予約サイズを超えた場合にガベージコレクションが実行される
マネージヒープ A B D F H 世代 1 世代 0 世代 0 のオブジェクトのみが検査され 生き残ったオブジェクトは世代 1 に移動する 世代 1 は検査していないので オブジェクト B は生き残る
マネージヒープ A B D F H I J K L 世代 1 世代 0
マネージヒープ A B D F H I L 世代 1 世代 0 ガベージコレクションを実行していくとこのように世代 1 が徐々に増加していく
マネージヒープ A B D F H I L M N O 世代 1 世代 0 世代 1 のサイズが上限を超えた時にガベージコレクションが発生したとしよう
マネージヒープ A D L M O 世代 2 世代 1 世代 0 世代 1~ 世代 0 のオブジェクトを検査する 世代 1 の到達可能オブジェクトは世代 2 となる 世代 0 の到達可能オブジェクトは世代 1 となる
世代別に GC を行うので すべてのオブジェクトを検査しなくてすむ
世代 0 の使用量が予約サイズを超えた 予約サイズは CLR が動的に決める System.GC をコードで実行 Windows が空容量低下の状況を報告
CLR は個々のオブジェクトをスモールオブジェクトかラージオブジェクトのどちらかであると見なす 現在 85,000 バイト以上をラージオブジェクト ただし 変更される可能性あり ラージオブジェクトはスモールオブジェクトと違うアドレス空間に割り当てられる ラージオブジェクト Large Object Heap(LOH) スモールオブジェクト Small Object Heap (SOH)
現時点では GC はラージオブジェクトに対してコンパクションを行わない ラージオブジェクトは割り当て後 すぐに世代 2 の一部とみなされる 世代 2 で GC が実行される時じゃないとラージオブジェクトに対して GC は行われない
ラージオブジェクトだとメモリの断片化 ( フラグメンテーション ) が発生します
Large Object Heap A B C D
Large Object Heap A B C D オブジェクト B,C が到達不能になった
Large Object Heap A D ガベージコレクション発生後 到達不能のオブジェクトは解放され 1 つの空き容量を作成する コンパクションは行わない
Large Object Heap A E D オブジェクト E の割り当て要求に対応するために作成した空き容量が使用できる
Large Object Heap このような形で長いこと使っていると コンパクションしないので LOH だと メモリの断片化が発生する可能性がある OutOfMemoryException がスルーされる可能性ある
Using System; Using System.Runtime; // LOH に対するコンパクションを要求 GCSettings.LOHCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; // GC が発生して LOH がコンパクションされる GC.Collect(): LOH もコンパクションできる そう.NET4.5.1 ならね!
オブジェクトが GC により回収対象になった後で オブジェクトのメモリが解放される前に何らかのコードを実行できる
class Hoge{ // Finalize メソッド ~Hode() { // } } C++ のデストラクタと似ていますが 動作は異なる C++ ではスコープを外れた時に 確実に呼ばれます C# では Finalize メソッドが実行されるタイミングは全く制御できません
マネージヒープ A B C D E F B C E F ファイナライセ ーションリスト F リーチャフ ルキュー Finalize メソッドが定義してあるオブジェクトを割り当てる際 型インス箪笥コンストラクターが呼ばれる前に Finalization リストにオブジェクトのポインタが配置される
マネージヒープ A B C D E F B C E F ファイナライセ ーションリスト F リーチャフ ルキュー C D F が参照されなくなった
マネージヒープ A B C E F B E ファイナライセ ーションリスト C F F リーチャフ ルキュー ガベージコレクションが実行されると Finalize メソッドのないオブジェクト D は削除され Finalize メソッドのあるオブジェクト C F は F リーチャフ ルキューへ参照が移動する
マネージヒープ A B C E F B E ファイナライセ ーションリスト C F F リーチャフ ルキュー
マネージヒープ A B C E F B E ファイナライセ ーションリスト F リーチャフ ルキュー F リーチャフ ルキューにデータが入ると Finalize 用のスレッドが動作してキューからデータを取り出して Finalize メソッドを実行します F リーチャフ ルキューから参照が消えて C,F オブジェクトは到達不能となる
マネージヒープ A B E B E ファイナライセ ーションリスト F リーチャフ ルキュー その次のタイミングのガベージコレクションでオブジェクトは削除される
Finalize を使うオブジェクトは削除されるまでに 2 回のガベージコレクションが必要になる
CLR オブジェクトヘッダーの構造 http://dotnetlogbook.blogspot.jp/2009/09/clr.html 大きなオブジェクトヒープの秘密 http://msdn.microsoft.com/ja-jp/magazine/cc53499 The Dangers of the Large Object Heap https://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-thelarge-object-heap/