明示的に呼び出されなかったGC
場合に収集されないファイナライズ可能なオブジェクトで問題が発生しました。オブジェクトが を実装している場合は明示的Dispose()
に呼び出す必要があることはわかっていますが、フレームワークに依存することは安全であり、オブジェクトが参照されなくなったときに収集できると常に考えていました。Dispose()
IDisposable
しかし、windbg/sos/sosex でいくつかの実験を行った結果、ファイナライズ可能なオブジェクトに対してGC.SuppressFinalize()が呼び出されなかった場合、ルート化されていなくても収集されないことがわかりました。そのため、ファイナライズ可能なオブジェクト (DbConnection、FileStream など) を広範囲に使用し、それらを明示的に破棄しないと、メモリ消費量が多すぎたり、OutOfMemoryException
.
サンプル アプリケーションは次のとおりです。
public class MemoryTest
{
private HundredMegabyte hundred;
public void Run()
{
Console.WriteLine("ready to attach");
for (var i = 0; i < 100; i++)
{
Console.WriteLine("iteration #{0}", i + 1);
hundred = new HundredMegabyte();
Console.WriteLine("{0} object was initialized", hundred);
Console.ReadKey();
//hundred.Dispose();
hundred = null;
}
}
static void Main()
{
var test = new MemoryTest();
test.Run();
}
}
public class HundredMegabyte : IDisposable
{
private readonly Megabyte[] megabytes = new Megabyte[100];
public HundredMegabyte()
{
for (var i = 0; i < megabytes.Length; i++)
{
megabytes[i] = new Megabyte();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~HundredMegabyte()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
}
public override string ToString()
{
return String.Format("{0}MB", megabytes.Length);
}
}
public class Megabyte
{
private readonly Kilobyte[] kilobytes = new Kilobyte[1024];
public Megabyte()
{
for (var i = 0; i < kilobytes.Length; i++)
{
kilobytes[i] = new Kilobyte();
}
}
}
public class Kilobyte
{
private byte[] bytes = new byte[1024];
}
10回の反復の後でも、メモリ消費量が高すぎて(700MBから1GBに)、反復回数が増えるとさらに高くなることがわかります。WinDBG を使用してプロセスにアタッチすると、すべての大きなオブジェクトがルート化されていませんが、収集されていないことがわかります。
明示的に呼び出すと状況が変わりますSuppressFinalize()
。メモリ消費量は高圧下でも 300 ~ 400MB 前後で安定しており、WinDBG はルート化されていないオブジェクトがなく、メモリが空いていることを示しています。
質問は次のとおりです。これはフレームワークのバグですか? 論理的な説明はありますか?
詳細:
各反復の後、windbg は次のことを示します。
- ファイナライズ キューが空です
- freachable キューが空です
- 第 2 世代には、前の反復からのオブジェクト (100 個) が含まれています
- 以前の反復からのオブジェクトはルート化されていません