Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Out-of-memory due to latency of unmanaged memory disposal?

Tags:

c#

.net

wpf

My application was crashing with out-of-memory exceptions and sometimes other exceptions probably also caused by running out of memory.

I reproduced the problem with this simple code:

  for (int i = 0; i < 100000; i++)
    var bmp = new RenderTargetBitmap(256, 256, 96, 96, PixelFormats.Default);

In theory this code should not crash because the bitmaps should be automatically garbage collected, but it crashes consistently when running in 32 bit mode.

The problem can be fixed like this:

  for (int i = 0; i < 100000; i++)
  {
    var bmp = new RenderTargetBitmap(256, 256, 96, 96, PixelFormats.Default);
    if (i % 500 == 0)
    {
      GC.Collect();
      GC.WaitForPendingFinalizers();
    }
  }

Of course this solution is contrary to the common wisdom that you shouldn't explicitly call GC.Collect, but I suspect that this is a scenario where it does actually make sense.

Can anyone offer any informed insight into this? Is there a better way of solving the problem?

like image 573
MetaMapper Avatar asked Mar 25 '14 15:03

MetaMapper


1 Answers

RenderTargetBitmap most likely has a native resource(s) associated with it. You've got plenty of managed memory (GC gets called every X bytes allocated) - the managed objects probably don't have enough memory use on their own to be interesting at all. So it must be the unmanaged part - I expect that it has a DirectX texture (or something similar) underlying, which will only be released when finalizers are executed.

However, since there's never enough managed memory pressure, the GC doesn't actually get called at all, and the native resources will not be released.

The weird thing is that RenderTargetBitmap isn't an IDisposable. That means you can't properly dispose of the native resources ASAP. So, it's more like a bug in WPF than in .NET itself.

That's just an assumption, though.

To address a comment, the GC most definitely doesn't wait for the method to exit first. Replacing RenderTargetBitmap with byte[] shows this working correctly when native resources aren't involved.

EDIT: I finally managed to find this in the BCL source code. To dispose of the native resources of RenderTargetBitmap, you have to call Clear. It will be freed eventually even without that (the native resources are on a safe handle), but if you're only allocating and deallocating RenderTargetBitmap, you're going to run out of texture / native memory long before you even get GC to run. So to answer your real-life question, simply call Clear on the bitmap when it's not needed anymore, and it should not hog memory anymore.

July 2015:

It seems that the original bug has been fixed - looking through 4.5.2 sources, the memory pressure is correctly applied and allocating tons of RenderTargetBitmaps should now cause GC to collect properly. Still no IDisposable implementation, though.

like image 160
Luaan Avatar answered Oct 21 '22 12:10

Luaan