Teaching an old dog new tricks: GC fun in Whidbey


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The GC does an amazing job of
managing managed memory… We really work hard to make sure pages stay hot, and
the memory is cached where you need it…. But the reality of the world is that
for many folks it is not just managed memory they have to deal with. style="mso-spacerun: yes"> Often you have to deal with memory
allocated by legacy APIs in an unmanaged heap which the GC does not directly
manage or external non-memory resources. style="mso-spacerun: yes"> We added a couple of neat features to the
CLR in Whidbey to help address these issues. style="mso-spacerun: yes">  I was just in the middle of adding
slides about these to my Design .NET Class Libraries class that I teach to WinFX
(and other) developers here at Microsoft and I thought I’d share about these
cool features… "urn:schemas-microsoft-com:office:office" />


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 



  1. style="MARGIN: 0in 0in 0pt; mso-list: l1 level1 lfo1; tab-stops: list .5in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Cheap managed object holding an
    expensive unmanaged object.

style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">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. style="mso-spacerun: yes"> 


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 


style="MARGIN: 0in 0in 0pt 1in; TEXT-INDENT: -0.25in; mso-list: l2 level2 lfo2; tab-stops: list 1.0in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Tahoma; mso-fareast-font-family: Tahoma"> style="mso-list: Ignore">• style="FONT: 7pt 'Times New Roman'">        
Useful
when you have a disproportionate ratio of managed, to unmanaged
resources


style="MARGIN: 0in 0in 0pt 1in; TEXT-INDENT: -0.25in; mso-list: l2 level2 lfo2; tab-stops: list 1.0in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Tahoma; mso-fareast-font-family: Tahoma"> style="mso-list: Ignore">• style="FONT: 7pt 'Times New Roman'">        
GC alters
it’s strategy, to increase the number of collections
performed


style="MARGIN: 0in 0in 0pt 1in; TEXT-INDENT: -0.25in; mso-list: l2 level2 lfo2; tab-stops: list 1.0in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Tahoma; mso-fareast-font-family: Tahoma"> style="mso-list: Ignore">• style="FONT: 7pt 'Times New Roman'">        
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">GC.RemoveMemoryPressure when your
object is freed, to allow the GC to return to its standard
strategy


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">class Bitmap
{


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   private long
_size;


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   Bitmap (string path )
{


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   style="mso-spacerun: yes">   _size = new
FileInfo(path).Length;


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   style="mso-spacerun: yes">  
GC.AddMemoryPressure(_size);


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   style="mso-spacerun: yes">   // other
work


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   }


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   ~Bitmap() {


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   style="mso-spacerun: yes">  
GC.RemoveMemoryPressure(_size);
style="mso-spacerun: yes">        
// other work


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   }


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">}


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">In the constructor we “add pressure”
to the GC in the form of the number of bytes the bitmap will store in unmanaged
memory and in the finalizer we remove that pressure.


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 



  1. style="MARGIN: 0in 0in 0pt; mso-list: l1 level1 lfo1; tab-stops: list .5in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Handling a limited number of
    resources.

style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">In some cases there exists only a
limited number of a resource (a HDC, Hwnd, database connection, etc) and you
want to manage your usage of those resources carefully, but you’d like to hide
that management from your users. style="mso-spacerun: yes"> The HandleCollector is here to help! It
essentially does the bean counting for you and kicks the GC when you start to
run out of resources to perform a collection to try to free some of the
resource.  Notice this basic
strategy was used privately by WinForms in V1 and is now made public in
Whidbey.


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> style="mso-spacerun: yes"> 


style="MARGIN: 0in 0in 0pt 0.5in; TEXT-INDENT: -0.25in; mso-list: l0 level1 lfo3; tab-stops: list .5in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Tahoma; mso-fareast-font-family: Tahoma"> style="mso-list: Ignore">• style="FONT: 7pt 'Times New Roman'">        
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">HandleCollector keeps track of a
limited number of handles


style="MARGIN: 0in 0in 0pt 1in; TEXT-INDENT: -0.25in; mso-list: l0 level2 lfo3; tab-stops: list 1.0in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Tahoma; mso-fareast-font-family: Tahoma"> style="mso-list: Ignore">• style="FONT: 7pt 'Times New Roman'">        
style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">typically, unmanaged resource
handles: HDCs, HWnds, etc


style="MARGIN: 0in 0in 0pt 0.5in; TEXT-INDENT: -0.25in; mso-list: l0 level1 lfo3; tab-stops: list .5in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Tahoma; mso-fareast-font-family: Tahoma"> style="mso-list: Ignore">• style="FONT: 7pt 'Times New Roman'">        
When you
allocate a new handle, call Add.


style="MARGIN: 0in 0in 0pt 0.5in; TEXT-INDENT: -0.25in; mso-list: l0 level1 lfo3; tab-stops: list .5in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Tahoma; mso-fareast-font-family: Tahoma"> style="mso-list: Ignore">• style="FONT: 7pt 'Times New Roman'">        
When you
freeing, call Remove


style="MARGIN: 0in 0in 0pt 0.5in; TEXT-INDENT: -0.25in; mso-list: l0 level1 lfo3; tab-stops: list .5in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Tahoma; mso-fareast-font-family: Tahoma"> style="mso-list: Ignore">• style="FONT: 7pt 'Times New Roman'">        
As you
add to the collector, it may perform a GC.Collect, to free existing handles,
based on the current count, and the number of resources
available


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">HandleCollector(string name,
int initialThreshold,  int
maximumThreshold);


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">name style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">: allows you to track each handle
type separately, if needed


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">initialThreshold style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">: the point at which collections
should begin being performed


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">maximumThreshold style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">: the point at which collections
MUST be performed. This should be set to the maximum number of available
handles


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">static readonly
HandleCollector GdiHandleType =
  
new HandleCollector( “GdiHandles”, 10, 50);


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> 


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">static IntPtr
CreateSolidBrush() {


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   IntPtr temp =
CreateSolidBrushImpl(…);


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">  
GdiHandleType.Add();


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">   return temp;


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">}


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> 


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">internal static void
DeleteObject(IntPtr handle) {


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">  
DeleteObjectImpl(handle);


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'"> style="mso-tab-count: 1">  
GdiHandleType.Remove();


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">}


 

Comments (19)

  1. MartinJ says:

    I would love to see managed code be informed when the GC is about to kick off a collection. Or, some way to determine when memory is starting to run low. That way, I could implement my own form of data caching that can give up references when things get tight.

    I realize that someone might abuse this ability to make the GC deterministic. But, the benefits could allow for applications that don’t become such memory hogs.

    -MartinJ

  2. Brad,

    HandleCollector is a half-assed fix that will simply not fix the issue:

    1. GC is asynchrnous and there are plenty of ways to run out of handles before GC has time to free them through finalization.

    2. It’s expensive, as forces collection along both dimensions (resources and memory) even though only one may be reaching its limits.

    But you guys are writing a new operating system. Why not dispense with archaic limits? There is no reason for pens, brushes, bitmaps and most other GDI (plus a few User) resources to be limited by anything but the memory they take up. (Fonts are probably an exception.)

    Also, some resource management patterns need to be built into the framework and checked by FxCop so that scew-ups like the one I described at http://www.jelovic.com/weblog/e119.htm don’t happen too often.

    Dejan

  3. I think there is a large potential for really messing the system up by mismatching calls to GC.AddMemoryPressure and GC.RemoveMemoryPressure. It would be *very* hard to figure out where the problem is. How about a single member function (maybe you’d have to inherit from some base object):

    this.SetMemoryPressure(int n)

    Now the GC knows how much memory the object is using at all times, and there is no chance of a library (someone else’s, of course:) messing up the system.

    In addition, the GC might use the information to call a finalizer earlier than it would otherwise. Maybe the GC could "keep an eye" on huge objects.

    -Jeremy

  4. First comment, does .AddMemoryPressure work on a global level (adding more maximum memory consumed), or does it attach this information to the instance in question. I’m assuming you might grab the class instance and assign the new memory pressure value to the class itself under the covers, but I want to make sure.

    Does .AddMemoryPressure scale to inheritance situations where a parent object (Bitmap), might set pressure based on the file size and then say a TwoLayerBitmap would get the chance to add additional pressure based on some secondary unmanaged buffers it creates?

    Memory pressure can get removed during the Dispose method or during Finalization. I’m assuming that you would need to do a RemoveMemoryPressure during a Dispose/Close operation, but during a Finalize, the GC should assume a RemoveMemoryPressure?

    As for the HandleCollector, is there anything this is buying us that we haven’t already done ourselves as .NET programmers? Is this just a helper class that handles calling the GC for us? Does it force the GC to only collect specific types of objects? It doesn’t appear so based on the code.

  5. mihailik says:

    Now Add/RemoveMemoryPressure don’t attach the pressure amount to object instance. I don’t know why. This ‘agnostic’ style really will produce bugs.

    What can be done? Just we need is wrapper base class, that will automatically ensure AddPressure/RemovePressure called in right scenario. I think, MS must ship such class in Widbey to prevent our bugs.

    Below is my vision. Just automatically Add in ctor and Remove in Dispose.

    This code is just simple demo. It need be tuned for locking/threading and for checking Add/Remove pair matches.

    public abstract class UnmanagedResource : IDisposable

    {

    readonly int m_PressureAmount;

    public UnmanagedResource(int pressureAmount)

    : this( pressureAmount, true )

    { }

    public UnmanagedResource(int pressureAmount, bool addPressureNow)

    {

    this.m_PressureAmount=pressureAmount;

    if( addPressureNow )

    ResourceAllocated();

    }

    protected void ResourceAllocated()

    {

    GC.AddMemoryPressure(PressureAmount);

    }

    protected void ResourceReleased()

    {

    GC.RemoveMemoryPressure(PressureAmount);

    }

    protected int PressureAmount

    {

    get { return m_PressureAmount; }

    }

    public void Dispose()

    {

    Dispose(true);

    }

    protected virtual void Dispose(bool disposing)

    {

    ResourceReleased();

    if( disposing )

    {

    GC.SuppressFinalize();

    }

    }

    ~UnmanagedResource()

    {

    Dispose(false);

    }

    }

  6. Not directly related, but has the GC version (srv and wks) been changed for Windows Services? I mean, if there is one place where we really need the server version it’s in Windows Services!

  7. Brad Abrams says:

    Yves, the runtime will choose what GC to load based on the machine we are running on… Multiproc get the server GC for example. You can also customize it on each process via a config file

  8. [.NET]Teaching an old dog new tricks: GC fun in Whidbey

  9. Flier Lu says:

    Why not use IoC pattern connected the managed object and the unmanaged resource? For example, define an interface IUnmanagedResource, which support GC got the unmanaged resource size with IUnmanagedResource.GetUnmanagedResourceSize function.

    public interface IUnmanagedResource

    {

    uint GetUnmanagedResourceSize();

    };

    The managed object implement the interface, and register itself to GC UnmanagedResource list with GC.RegisterForUnmanagedResource, like GC Finalizer list.

    // GC.RegisterForUnmanagedResource(IUnmanagedResource res)

    class Bitmap : IUnmanagedResource

    {

    private long _size;

    uint GetUnmanagedResourceSize()

    {

    return _size;

    }

    Bitmap (string path )

    {

    _size = new FileInfo(path).Length;

    GC.RegisterForUnmanagedResource(this);

    // other work

    }

    }

    So GC could calculate the unmanaged size, and known which unreachable object has how many unmanaged resource.

  10. Robin Maffeo says:

    A minor correction to Brad’s comment on wks/svr GC selection… As he mentioned, we added a new config flag in Whidbey and v1.1 SP1 where you can choose which flavor of the collector you want. However, absent any guidance, workstation GC is still the default. The runtime won’t automatically select server GC for you since the assumption is an interactive application unless otherwise specified.

  11. Antonello Tesoro says:

    Assume we’ve implemented the Dispose Pattern in our type which holds references to un unmanaged scarce resource.

    When the HandleCollector wants to free as much of our unmanaged resource as it can ( because for instance we’ve reached the minThreshold ) it has no other choices but to perform a "Full Collection" so that the Finlize method of the instances of our type will be called. This carries on the undesired consequences we’re all well aware of, such as incorrect Generation promotion of some objetcs, the performance penalty of a full GC collection, the Finalization of objetcs of other types we didn’t need to Finalize at this time; all This, just to have the Finalize method of all the live instances of our type to be called!

    What is really needed in those cases is something like:

    public static void GC.FinalizeType( Type typeToFinalize, bool runSync );

    This method should suspend all the threads and walk the managed heap using the very same algorithms the GC already uses in the standard Collection, to find out which instances of our types aren’t reachable; and only call the Finalize mthod on those instances without performing a full collection or reclaiming any memory. The second flag asks the method to wait until all those objects have been fully finalized ( this is needed if we really ran out of resources and cannot continue the execution of our program without freing any ).

    I Belive this kind of solution will get the same result of the GC.Collect without the side effects and the performance penalties of a full collection.

  12. Avnrao’s Blog has a post GC on small managed resource holding large unmanaged resources… By default small objects will have a low priority as far as GC is concerned but in Whidbey onwards you can boost this by calling GC.AddMemoryPressure….

  13. Here’s the next .NET Framework 2.0 performance guideline for review from Prashant Bansode, Bhavin Raichura,

  14. Here’s the next .NET Framework 2.0 performance guideline for review from Prashant Bansode, Bhavin Raichura,