0

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?

Community
  • 1
  • 1
Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • Thats an extremely quick loop, is it possiple you haven't gotten the `hbitmap` before you attempt to delete it? – Sayse Sep 01 '14 at 13:57
  • @Sayse, what you propose? To check for `null` or what? – Sinatr Sep 01 '14 at 13:57
  • 3
    Use a memory profiler? – Sriram Sakthivel Sep 01 '14 at 13:59
  • /\ Profiler sounds good, for the sake of testing (only!) it may be worth adding `Thread.Sleep` – Sayse Sep 01 '14 at 14:00
  • @SriramSakthivel, I thought given example is enough to someone more experienced to tell me what is wrong. I have express edition and very limited knowledge of what memory profiler can tell me in this case... – Sinatr Sep 01 '14 at 14:00
  • @Sayse, putting `Sleep` into timer is lol =D. Tell me which frequency you think I should use? I though `20 Hz` is pretty ok for windows. – Sinatr Sep 01 '14 at 14:04
  • I'm not sure if this is the case so let met share my past experience in the comments: I was developing a C# app that processes lots of photos (think batch thumbnail generation) with multithread. The process just went 100MB..200MB..all the way up to 2GB and crash. Turns out the .NET GC is not fast enough to claim memory in this case (I'm using all CPU cores to go as fast as possible). When I call `Bitmap.Dispose()` manually the problem is solved. – kevin Sep 01 '14 at 14:15
  • @Sinatr - It felt dirty saying it too, but either doing that or changing the interval would eliminate one possibility (Note these two things do different things) – Sayse Sep 01 '14 at 14:24

2 Answers2

6

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.

Community
  • 1
  • 1
Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78
  • That *pressure* thing is something new to me, thanks. I really like your second solution (without direct `GC` call), works smoothly! – Sinatr Sep 01 '14 at 14:35
  • Noticed one *bad* thing about second solution: memory allocation may grow high (sometimes even causing `OutOfMemoryException`) and stop there, keeping that huge amount of memory allocated. This doesn't happens with timer, only when updating occurs on user actions (if user move mouse and on every `MouseMove` event something is redrawn, then said problem may happens). Perhaps has something to do with priorities. Anyway, solution to this problem is to use your first solution (trigger `GC.Collect(3)` directly) on a timed/counted basis (to example, every 10th redraw - collect). – Sinatr Sep 02 '14 at 08:53
  • Erti +Erti @Erti - Thank you for teaching me Extreme Garbage Collection. This is entirely awesome. Perfect solution for what ailed me. A thousand plusses unto you. – Jason P Sallinger Jul 15 '16 at 18:31
2

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().

Tim
  • 126
  • 1
  • 5
  • Second timer may not run *fast enough*, but thanks for an idea (which is actually right, no memory leak in code, simple too much allocations). – Sinatr Sep 01 '14 at 14:31