我遇到了一个可终结对象的问题,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 代包含来自先前迭代的对象(数百个)
- 来自先前迭代的对象是无根的