I've read about this issue on MSDN and on CLR via c#.
Imagine we have a 2Mb unmanaged HBITMAP allocated and a 8 bytes managed bitmap pointing to it. What's the point of telling the GC about it with AddMemoryPressure if it is never going to be able to make anything about the object, as it is allocated as unmanaged resource, thus, not susceptible to garbage collections?
The point of AddMemoryPressure is to tell the garbage collector that there's a large amount of memory allocated with that object. If it's unmanaged, the garbage collector doesn't know about it; only the managed portion. Since the managed portion is relatively small, the GC may let it pass for garbage collection several times, essentially wasting memory that might need to be freed.
Yes, you still have to manually allocate and deallocate the unmanaged memory. You can't get away from that. You just use AddMemoryPressure to ensure that the GC knows it's there.
Edit:
Well, in case one, I could do it, but it'd make no big difference, as the GC wouldn't be able to do a thing about my type, if I understand this correctly: 1) I'd declare my variable, 8 managed bytes, 2mb unmanaged bytes. I'd then use it, call dispose, so unmanaged memory is freed. Right now it will only ocuppy 8 bytes. Now, to my eyes, having called in the beggining AddMemoryPressure and RemoveMemoryPressure at the end wouldn't have made anything different. What am I getting wrong? Sorry for being so anoying about this. -- Jorge Branco
I think I see your issue.
Yes, if you can guarantee that you always call Dispose
, then yes, you don't need to bother with AddMemoryPressure and RemoveMemoryPressure. There is no equivalence, since the reference still exists and the type would never be collected.
That said, you still want to use AddMemoryPressure and RemoveMemoryPressure, for completeness sake. What if, for example, the user of your class forgot to call Dispose? In that case, assuming you implemented the Disposal pattern properly, you'll end up reclaiming your unmanaged bytes at finalization, i.e. when the managed object is collected. In that case, you want the memory pressure to still be active, so that the object is more likely to be reclaimed.
It is provided so that the GC knows the true cost of the object during collection. If the object is actually bigger than the managed size reflects, it may be a candidate for quick(er) collection.
Brad Abrams entry about it is pretty clear:
Consider a class that has a very small
managed instance size but holds a
pointer to a very large chunk of
unmanaged memory. Even after no one
is referencing the managed instance it
could stay alive for a while because
the GC sees only the managed instance
size it does not think it is “worth
it” to free the instance. So we need
to “teach” the GC about the true cost
of this instance so that it will
accurately know when to kick of a
collection to free up more memory in
the process.
Put it this way, still assuming the 8 byte managed objects each referring to a 2 MB unmanaged image. The GC might wait a long time before collecting hundreds or thousands of the little managed objects, because they are so small. That would mean that also hundreds or thousands of linked 2 MB unmanaged chunks will stay alive, awaiting removal. That could become a huge problem. By adding 2 MB of memory pressure in the constructor you will make GC think that the managed object is not 8 bytes big but rather 8 bytes + 2 MB. That will trigger collection way earlier.
Don't forget the Remove call.
Of course if you dispose yourself then you won't need all that.
These methods allow the runtime to have some sense of how much unmanaged memory is being allocated by the process. Without calling these functions, it may not be able to see the true amount of unmanaged memory in use within the process.
However I disagree with the other answers here regarding an association between the memory being referred to and a particular GC object.
Consider:
var buffer = IntPtr.Zero;
try
{
buffer = Marshal.AllocHGlobal(size);
GC.AddMemoryPressure(size);
// ... use buffer ...
}
finally
{
Marshal.FreeHGlobal(buffer);
GC.RemoveMemoryPressure(size);
}
There is no way for the GC to assign the size
to a specific object. This code could even exist in a static method.
Therefore I assert that the statement we need to “teach” the GC about the true cost of this instance so that it will accurately know when to kick of a collection to free up more memory in the process is incorrect and misleading.
Instead this method might cause the GC to collect earlier than it would otherwise, in an effort to avoid running out of memory.