将GC.AddMemoryPressure与非托管资源一起使用有什么意义?

时间:2009-07-19 03:36:26

标签: c# .net vb.net garbage-collection

我已经通过c#在MSDN和CLR上了解了这个问题。

想象一下,我们分配了一个2Mb非托管HBITMAP和一个指向它的8字节托管位图。使用AddMemoryPressure告诉GC有什么意义,如果它永远无法对该对象做任何事情,因为它被分配为非托管资源,因此不容易被垃圾收集?

4 个答案:

答案 0 :(得分:12)

提供的是GC在收集过程中知道对象的真实成本。如果对象实际上大于托管大小反映的对象,则它可能是快速(呃)收集的候选对象。

Brad Abrams entry非常清楚:

  

考虑一个非常小的类   托管实例大小,但持有   指向一大块的   非托管内存。即使没有人   正在引用托管实例   可以活一段时间,因为   GC只看到托管实例   尺寸它不认为它是“值得的   它“释放实例。所以我们需要   关于真正的成本“教”GC   这个实例,以便它会   准确地知道什么时候踢一个   收集以释放更多的内存   这个过程。

答案 1 :(得分:9)

AddMemoryPressure的目的是告诉垃圾收集器,该对象分配了大量内存。如果没有管理,垃圾收集器就不知道了;只有管​​理部分。由于托管部分相对较小,因此GC可能会多次通过垃圾回收,实质上浪费了可能需要释放的内存。

是的,您仍然需要手动分配和取消分配非托管内存。你无法摆脱这种局面。您只需使用AddMemoryPressure来确保GC知道它在那里。

修改

  

嗯,在第一种情况下,我可以做到,但它没有什么大的区别,因为如果我理解这一点,GC就无法对我的类型做任何事情:1)我将声明我的变量,8个托管字节,2mb非托管字节。我然后使用它,调用dispose,因此释放了非托管内存。现在它只会占用8个字节。现在,在我看来,在最后调用AddMemoryPressure和RemoveMemoryPressure时,不会有任何不同。我错了什么?很抱歉这对此很感兴趣。 - Jorge Branco

我想我看到了你的问题。

是的,如果您可以保证始终致电Dispose,那么是的,您不需要为AddMemoryPressure和RemoveMemoryPressure打扰。没有等价,因为引用仍然存在,并且永远不会收集类型。

尽管如此,为了完整起见,您仍然希望使用AddMemoryPressure和RemoveMemoryPressure。例如,如果您班级的用户忘记拨打Dispose,该怎么办?在这种情况下,假设您正确实现了Disposal模式,您将最终在完成时回收您的非托管字节,即收集托管对象时。在这种情况下,您希望内存压力仍然有效,以便更有可能回收对象。

答案 2 :(得分:1)

这样说,仍假设8字节管理对象,每个管理对象指的是2 MB非托管图像。在收集数百或数千个小型托管对象之前,GC可能会等待很长时间,因为它们非常小。这意味着数百或数千个链接的2 MB非托管块将保持活跃状态​​,等待删除。这可能会成为一个巨大的问题。通过在构造函数中添加2 MB的内存压力,您将使GC认为托管对象不是8字节大,而是8字节+ 2 MB。这将提前触发收集方式。

不要忘记删除电话。

当然,如果你处理好自己,那么你就不需要这么做了。

答案 3 :(得分:0)

这些方法允许运行时了解进程分配了多少非托管内存。如果不调用这些函数,它可能无法在进程中看到正在使用的非托管内存的真实数量。

但是,我不同意这里关于被引用的内存与特定GC对象之间的关联的其他答案。

考虑:

var buffer = IntPtr.Zero;
try
{
    buffer = Marshal.AllocHGlobal(size);
    GC.AddMemoryPressure(size);

    // ... use buffer ...
}
finally
{
    Marshal.FreeHGlobal(buffer);
    GC.RemoveMemoryPressure(size);
}

GC无法将size分配给特定对象。这段代码甚至可以存在于静态方法中。

因此,我断言声明我们需要“教”GC关于此实例的真实成本,以便它准确地知道何时踢出一个集合来释放更多的内存是不正确和误导。

相反,此方法可能会导致GC比其他方式更早收集,以避免内存不足。