ガベージコレクタ
実はあまりよく調べたことがないので、復習を兼ねて。
GCが動作するタイミングは、
というケースがありますが、その中の1についてみてみます。
using System; using System.Threading; class Program { static void Main(string args) { // 100ms毎にHelloを表示 new Timer(delegate { Console.WriteLine("Hello"); }, null, 0, 100); // メモリを確保しまくる // Generation0がメモリ不足になるとGCが走る // するとどこからも参照されていないTimerオブジェクトが死ぬ object o = new byte[80*1024]; Console.WriteLine("o is generation {0}", GC.GetGeneration(o)); byte b = null; for (int i = 0; i < 15; ++i) { Thread.Sleep(200); // 85kb以上はラージヒープ行きなので、それ以下のサイズに。 b = new byte[85 * 1000 - 12 -1]; Console.WriteLine(GC.GetTotalMemory(false)); } byte[] c = b; Console.WriteLine("o is generation {0}", GC.GetGeneration(o)); Console.ReadLine(); } } /* 結果 o is generation 0 Hello Hello 320520 Hello Hello ... ... 1170640 Hello 1255652 Hello Hello 312620 405824 490836 o is generation 1 */
結果に注目すると、最後の方で使用中のメモリが1255652から312620に減少し、オブジェクトoがGeneration1になっています。また、Helloが表示されていないことから、どこからも参照されていなかったTimerオブジェクトがゴミ集めされてしまったことが分かります。
あと、本題とは違いますが、例のようなTimerの使い方をするとGCに回収されてしまいますので、回収されたくない場合はGC.KeepAliveなどを使ってオブジェクトの参照を保持するようにしなければなりません。
ちなみにコメントにあるように85kb以上のオブジェクトはラージヒープという別の領域に確保されます。これは、ガベージコレクト時にメモリの解放だけでなく、オブジェクトが削除されたことで虫食いになった領域を詰めて連続した空き領域を作る処理(コンパクション)を行いますが、これを大きいオブジェクトに対して行うとパフォーマンスに悪影響を与えるため、ラージヒープという別の領域を用意し、その領域はコンパクションを行わないようになっています。
ところで、ラージヒープに確保されたオブジェクトのGenerationは何だと思います?
using System; class Program { static void Main() { byte b1 = new byte[85 * 1000 - 12]; Console.WriteLine("b1 is generation {0}", GC.GetGeneration(b1)); byte b2 = new byte[85 * 1000 - 12 -1]; Console.WriteLine("b2 is generation {0}", GC.GetGeneration(b2)); } } /* 結果 b1 is generation 2 b2 is generation 0 */
このようにGeneration2扱いだったりします。