我正在尝试编写一个包装了Marshal.AllocHGlobal
分配的缓冲区的类。我实现了IDisposable
接口,并添加了一个终结器,当我不再需要它时(当对象超出范围时),它应该释放内存。
当我测试类时,GC不会调用我的类的终结器或Dispose方法,即使它们超出了范围。结果,我得到了OutOfMemoryException
。
为什么GC不会调用终结器,为什么内存不会被释放?
这是一个简短的例子来说明问题。在示例中,没有任何内容写入控制台(Unhandled Exception: OutOfMemoryException.
除外)
class Buffer : IDisposable
{
public IntPtr buf { get; set; }
public Buffer()
{
buf = Marshal.AllocHGlobal(4 * 1024 * 1024);
}
~Buffer()
{
Console.WriteLine("Finalizer called");
Dispose(false);
}
public void Dispose()
{
Console.WriteLine("Dispose called");
Dispose(true);
GC.SuppressFinalize(this);
}
internal virtual void Dispose(bool disposing)
{
if (buf != IntPtr.Zero)
{
Console.WriteLine("Releasing memory");
Marshal.FreeHGlobal(buf);
buf = IntPtr.Zero;
}
}
}
class Program
{
static void Main(string[] args)
{
while(true)
{
Buffer b = new Buffer();
Thread.Sleep(20);
}
}
}
编辑:这是我的测试程序崩溃时的.NET性能计数器:
答案 0 :(得分:5)
您需要告诉垃圾收集器,具有单个IntPtr
字段的非常小的托管对象在非托管内存方面的成本很高。目前,垃圾收集器幸福地没有意识到每个小型托管对象都使用大量非托管内存而没有理由进行任何收集。
分配非托管内存时可以使用GC.AddMemoryPressure,释放非托管内存时可以使用GC.RemoveMemoryPressure。
答案 1 :(得分:2)
当满足下列条件之一时,就会发生垃圾收集:
此外,垃圾收集器仅跟踪托管堆上的内存,因此对于此程序,唯一可以触发GC的条件是第一个。
我编译了程序,如果目标CPU是x86,当进程的私有字节达到2G时,它将通过内存不足异常。当我运行程序时,我注意到私有字节快速增加,但工作集增加得非常慢,而且系统物理内存使用量也增加得非常慢。
作为私有字节和工作集,这个post解释了: Private Bytes指的是进程可执行文件要求的内存量 - 不一定是它实际使用的量。他们是私人的"因为它们(通常)排除了内存映射文件(即共享DLL)。但是 - 这就是问题所在 - 他们不一定要排除这些文件分配的内存。没有办法判断私有字节的更改是由于可执行文件本身还是由于链接库。专用字节也不仅仅是物理内存;它们可以被分页到磁盘或备用页面列表(即不再使用,但也没有被分页)。
工作集是指进程使用的总物理内存(RAM)。但是,与专用字节不同,这还包括内存映射文件和各种其他资源,因此它比专用字节的测量精度更低。这与在任务管理器"内存使用情况"中报告的值相同。并且近年来一直是无休止混乱的根源。工作集中的记忆是"物理"从某种意义上说,它可以在没有页面错误的情况下解决;但是,备用页面列表在物理上仍然在内存中,但未在工作集中报告,这就是为什么您可能会看到"内存使用情况"当你最小化一个应用程序时,它会突然掉落。
Marshal.AllocHGlobal只会增加私有字节,但工作集仍然很小,它也不会触发GC。
答案 2 :(得分:0)
IDisposable是声明性的,只有在实际发生垃圾收集时才会调用dispose方法。
Yoy可以强制进行垃圾收集,为此你需要调用
GC.Collect的
http://msdn.microsoft.com/en-us/library/xe0c2357(v=vs.110).aspx
我还建议使用性能计数器来查看应用程序内存消耗情况,并查看是否已调用GC。看这里怎么做http://msdn.microsoft.com/en-us/library/x2tyfybc(v=vs.110).aspx