Unmanaged objects and memory usage


I few years ago I wrote a small utility class named Win32Window, so that I could write
an automated tool to close down some of the dialogs that would appear whenever outlook
lost a server connection (which was often, since it happened whenever I opened or
closed my laptop). It wraps some of the high-level Win32 window handling functions.
As part of that, I wrote a couple of routines to do screen and window capture, though
I’ve never really used them.

Yesterday I got an email from someone who was using the routine for real, and it was
eating his system’s lunch (not actually the term he used, but words to those effects).
It’s always unfortunate when a poorly-coded sample makes it out into the wild. One
would hope not to have any of those, but I learned in writing my book that a) It’s
not possible to be perfect on technical details, b) that if you cover enough details,
you will make some mistakes, and c) some of them will be howlers, and make people
think you are a total moron.

I was hoping this case wouldn’t be that bad.

I opened up my project, modified my test app so that it would capture screenshows
on a timer, started up perfmon to look at the .NET memory counters, and set it running,
with one screenshot ever 100 mS. Though the counters on perfmon barely budged, in
about 15 seconds things were getting slow, and soon after that, things got *really*
slow – so slow, that I had to reboot.

In looking at my code, I found that I hadn’t released some of the unmanaged graphics
items, but fixing that issue didn’t change the behavior at all. I switched to watching
the normal (ie not .net) memory counters, and found that they went up a big chunk
every time I did a screenshot.

I played around with the excelled CLR Profiler, but the current problem with the CLR
profiler is that it doesn’t have a very good delta view, and it’s optimized for looking
only at managed objects, not managed wrappers, which tend to be small in the managed
world but big in the unmanaged world (think of a bitmap. On the managed size, there
is a single IntPtr reference to the unmanaged object, which might easily exceed a
megabyte in size). So, I didn’t get anywhere with it.

I next added a “CLR” button in my app, to call:

GC.Collect();
GC.WaitForPendingFinalizers();

If my hunch was correct, this would find that managed object and finalize it, and
the memory use would go down. That proved to be correct, which led me on a hunt further.

The final issue was actually two issues. First, I wasn’t freeing a Graphics object
that I should have. Second, I had code that was something like:

desktopWindow.Image = Win32Window.DesktopAsBitmap;

which works fine, but has the side effect of never disposing the
current bitmap.

But I thought managed code meant I didn’t have to think about such things…

Unfortunately, this is a case where our current solution doesn’t work very well. In
an ideal world, the GC would have cleaned up after those huge bitmaps, but unfortunately,
it has no way of knowing how expensive the underlying unmanaged objects really are,
so I could allocate tons of new Images with creating enough memory pressure to force
a GC to occur. That’s somewhat unfortunate.

The fix for the second issue is to call dispose on the old image. That gets me back
to a steady-steate situation. The code will make its way up to the GDN archive in
the next few days; if you’re using this code, drop me a line and I’ll send you an
updated version.

Comments (6)

  1. Kevin Westhead says:

    I think this is a common problem when interacting with GDI objects in particular via managed code. For example, ImageList.ImageCollection.getItem(Int32) returns a new Bitmap object that the caller should dispose of when finished with, however this isn’t necessarily clear from the documentation. Also, while on the subject of image lists, ImageList.Draw(Graphics, Int32, Int32, Int32) and ImageList.Draw(Graphics, Point, Int32) seem like wrappers around ImageList_DrawEx. ImageList.Draw(Graphics, Int32, Int32, Int32, Int32, Int32) however can potentially create a new Bitmap object that never gets disposed of, and therefore you can unknowingly increase your GDI object count.

  2. Steve Bolton says:

    I had a similar problem with a slideshow viewer I wrote in C#. I used a couple of bitmap objects to show the current image and the image to be displayed next, and if I ran the show fast the old bitmap objects weren’t being collected so I had to explicitly call dispose – very nasty!

  3. Alienated VB developer says:

    For a "new platform", it sure appears .NyET has inherited nearly all of Java’s fundamental fatal flaws in regards to resource management! I’ll stick to real languages that support real object destructors, fuck you all very much.

  4. JiMMy says:

    I thought C# objects have destructor?

  5. imagelist hater says:

    What is the deal with imagelists? They generally suck.

    if I say imageList.Images[1] = new Bitmap(10,10), what happens to the old bitmap that was in slot 1? does it get disposed? or am I supposed to do that first?

  6. MBA says:

    Helpful For MBA Fans.