Sinatr
Sinatr

Reputation: 22008

Memory leak, but where?

I can't understand what is leaking here

using GDI = System.Drawing;

public partial class MainWindow : Window
{
    [DllImport("gdi32.dll")]
    private static extern bool DeleteObject(IntPtr obj);

    public MainWindow()
    {
        InitializeComponent();

        var timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(50) };
        timer.Tick += (s, a) =>
        {
            using (var bitmap = new GDI.Bitmap(1000, 1000))
            {
                var hbitmap = bitmap.GetHbitmap();
                var image = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
                image.Freeze();
                DeleteObject(hbitmap);
            }
        };
    timer.Start();
}

bitmap? Disposed. hbitmap? Deleted. image? Frozen and it's not IDisposable.

The fact is, this application will crash (on my PC after just ~20 seconds of running)

An unhandled exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll

Additional information: Out of memory.

Any ideas?

Upvotes: 0

Views: 1066

Answers (3)

Anonymous
Anonymous

Reputation: 17

CreateBitmapSourceFromHBitmap occasionally throws exceptions. Therefore, an exception handler is needed to reliably delete the IntPtr. Solution ...

Upvotes: 1

Tim
Tim

Reputation: 125

It is possible that the Garbage Collector is not freeing up the disposed bitmaps fast enough. You are creating 400 1000*1000 bitmaps every 20 seconds which could consume up to 1.6GB of memory. Maybe try adding a second timer that runs every 1000 milliseconds that makes a call to GC.Collect().

Upvotes: 2

Erti-Chris Eelmaa
Erti-Chris Eelmaa

Reputation: 26298

As far I can tell, there are no leaks going on. The problem is that you're allocating big C# objects fast and garbage collector kicks in way too late?

Here are few relevant topics:

Avoiding OutOfMemoryException during large, fast and frequent memory allocations in C#

and here is useful thread:

Garbage Collection not happening even when needed

If you kick GC.Collect(with generations 0..3), your memory consumption will be fixed:

    while (true)
    {
        Thread.Sleep(5);

        using (var bitmap = new GDI.Bitmap(1000, 1000))
        {
            var hbitmap = bitmap.GetHbitmap();
            var image = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());

            image.Freeze();


          DeleteObject(hbitmap);
        }

        Console.WriteLine("Current memory consumption" + GC.GetTotalMemory(false));
        GC.Collect(3);
    }

and output:

Current memory consumption156572
Current memory consumption156572
Current memory consumption156572
Current memory consumption156572

The real problem is that GC does not know about your unmanaged allocations, even if you free them. You need to add memory pressure and let the GC know about it:

 var width = 1000;
                var height = 1000;

                using (var bitmap = new GDI.Bitmap(width, height))
                {
                    var hbitmap = bitmap.GetHbitmap();
                    var allocatedSize = width*height*4; // each pixel takes ~4 bytes?!
                    GC.AddMemoryPressure(allocatedSize);

                    var image = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty,
                        BitmapSizeOptions.FromEmptyOptions());

                    image.Freeze();


                    DeleteObject(hbitmap);
                    GC.RemoveMemoryPressure(allocatedSize);
                }

Letting GC know about underlying unmanaged memory helps to make sure GC kicks in at right places.

Upvotes: 6

Related Questions