Question

I would like to know why the garbage collector cannot keep track of the amount of memory used by the unmanaged part of a bitmap and release this resource when needed.

I discovered it when a loop in a background thread called the following function causing an exception after a while:

    private delegate void setImageCallback();
    private void showFrame()
    {
        if (pboxCam.InvokeRequired)
        {
            this.Invoke(new setImageCallback(showFrame));
        }
        else
        {
            Bitmap bmp = new Bitmap(getBitmapFromCam());
            Graphics g = Graphics.FromImage(bmp);
            g.DrawRectangle(RectanglePen, 10, 10, 50, 30);
            g.Dispose();
            pboxCam.Image = bmp;
        }
    }

Then I tried to release bmp at the end of the function, but the picturebox doesn’t show up the image anymore. The solution below does work, but there is something I don’t like. What do you think?

    Bitmap bmp;

    private delegate void setImageCallback();
    private void showFrame()
    {
        if (pboxCam.InvokeRequired)
        {
            this.Invoke(new setImageCallback(showFrame));
        }
        else
        {
            if (bmp!=null) bmp.Dispose();
            bmp = new Bitmap(getBitmapFromCam());
            Graphics g = Graphics.FromImage(bmp);
            g.DrawRectangle(RectanglePen, 10, 10, 50, 30);
            g.Dispose();
            pboxCam.Image = bmp;
        }
    }
Was it helpful?

Solution

You're doing it right. The GC is tracking the Bitmap object, and it will release it eventually. The key word being "eventually".

To reclaim the managed bmp instance, it first has to run into memory pressure. That occurs at about 1 MiB for gen-0 collections or so. I'm not sure about the precise number, but the key element is that it's a lot. You're going to have thousands of bitmaps, if not more, before you even get to gen-0 collection - and you usually need a gen-2 to execute finalizers (though take that with a grain of salt, I have no idea where I found that information).

Even when the GC does eventually run (in practice, you'll run out of memory long before that), it will start executing the finalizers on a separate thread. It has no idea about the unmanaged resources, so it has to rely on the finalizer code, which it runs serially. It has no idea how much unmanaged memory is allocated at all, and cannot figure that in its memory checks.

So, the key thing is to always run Dispose on IDisposable objects. The GC might not even get the chance to start the finalizers before you run out of memory, because it's only induced by managed memory pressure. It has no idea about the unmanaged memory (if any). That's not the GCs fault, it really can't tell - after all, in your precise case, the memory is shared. The GC can't know if it even should release that memory - that's up to the Bitmap.Finalize to decide.

That's also the reason why you really want to make everything that uses any IDisposable or unmanaged memory, also IDisposable - to make sure the caller can release the native resource as soon as possible.

This doesn't apply just to unmanaged memory - waiting for the GC to dispose of eg. a file handle or a socket connection is a bad idea, since you really don't want to hold those longer than necessary.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top