Image loading memory leak with C#
Asked Answered
C

3

10

I have a memory leak issue in my application which loads a large amount of images. I'm rather new to C#, and thought my days of memory leak issues were past. I can't figure out the problem - maybe I'm using some unmanaged modules which I'm not handle correctly?

To illustrate my problem I've simplified the core of what causes the problem and moved this to a clean project. Note that this is all silly code which doesn't reflect the original application it came from. In the test application I have 2 buttons, triggering two events.

Button 1 - Create: Setting an object to the datacontext. This will load the images and keep them alive by setting the object to the DataContext:

var imgPath = @"C:\some_fixed_path\img.jpg";
DataContext = new SillyImageLoader(imgPath);

Button 2 - CleanUp: My understanding is that if I let go of the reference holding the SillyImageLoader which again holds the images, then this will be deleted. I also explicitly trigger garbage collection just to see immediately the amount of memory after dropping the reference.

DataContext = null; 
System.GC.Collect();

When testing I'm loading a 974KB jpeg image. Holding 30 bitmap representations of this boosts the memory usage of my application from ~18MB to ~562MB. Ok. But when I hit cleanup the memory drops only to ~292MB. If I repeat Create+CleanUp I'm left with another ~250MB memory. So obviously something is still held by someone.

Here is the SillyImageLoader-code:

namespace MemoryLeakTest
{
    using System;
    using System.Drawing;
    using System.Windows;
    using System.Windows.Interop;
    using System.Windows.Media.Imaging;

    public class SillyImageLoader
    {
        private BitmapSource[] _images; 

        public SillyImageLoader(string path)
        {
            DummyLoad(path);
        }

        private void DummyLoad(string path)
        {
            const int numberOfCopies = 30;
            _images = new BitmapSource[numberOfCopies];

            for (int i = 0; i < numberOfCopies; i++)
            {
                _images[i] = LoadImage(path);
            }
        }

        private static BitmapSource LoadImage(string path)
        {
            using (var bmp = new Bitmap(path))
            {
                return Imaging.CreateBitmapSourceFromHBitmap(
                    bmp.GetHbitmap(),
                    IntPtr.Zero,
                    Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
            }            
        }
    }
}

Any ideas? The problem seems to be with the BitmapSource. Holding only the Bitmap there is no memory leak. I am using BitmapSource to be able to set this to the Source property of an Image. Should I do this differently? If so - I'd still like to know the answer the memory leak.

Thanks.

Chura answered 11/11, 2009 at 12:18 Comment(6)
Who's calling Dispose on the BitmapSource returned from LoadImage?Nonconformance
I thought this, indeed posted an answer based on it but I can't see a dispose on BitmapSource (I've deleted the answer)Jacobsen
How are you monitoring the memory usage of you app? Task manager?Asphalt
Yes - monitoring in the most primitive way - using Task manager. There are probably better tools out there, but at least in this case it does the job. You want to suggest a better tool/method? :-)Chura
Task manager can be can be misleading. When you allocate an object, .net will allocate some memory - which will ultimately involve asking windows to allocate some memory to the process. This memory becomes part of your apps "working set" - the total memory allocated to the process. When you release your last reference to the object and the GC frees it, the memory on the clr heap will be flagged as free, but it won't be returned for use by windows - it stays part of the working set. Because task manager displays the size of your working set, you can get a misleading impression of memory usageAsphalt
On the tools front I can recommend YourKit Profiler (yourkit.com/.net/profiler/index.jsp) a personal license is quite cheap and it really helps track down resources that weren't freed and what objects are keeping references to them.Dreyfus
D
13

When you call

bmp.GetHbitmap()

a copy of the bitmap is created. You'll need to keep a reference to the pointer to that object and call

DeleteObject(...)

on it.

From here:

Remarks

You are responsible for calling the GDI DeleteObject method to free the memory used by the GDI bitmap object.


You may be able to save yourself the headache (and overhead) of copying the bitmap by using BitmapImage instead of BitmapSource. This allows you to load and create in one step.

Dreyfus answered 11/11, 2009 at 12:31 Comment(1)
This is absolutely correct and made the memory issues go away! Thx! And thx to the others with similar answers!You're right that I could use BitmapImage in this particular example. However, in my actual application I don't have a path to an image file, but fetch Bitmaps from another object of a 3rd party library. There's not really much I can do about this. So I can't use an Uri - I need to use a Bitmap..Chura
A
7

You need to call the GDI DeleteObject method on the IntPtr pointer returned from GetHBitmap(). The IntPtr returned from the method is a pointer to the copy of the object in memory. This must be manually freed using the following code:

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

private static BitmapSource LoadImage(string path)
{

    BitmapSource source;
    using (var bmp = new Bitmap(path))
    {

        IntPtr hbmp = bmp.GetHbitmap();
        source = Imaging.CreateBitmapSourceFromHBitmap(
            hbmp,
            IntPtr.Zero,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());

        DeleteObject(hbmp);

    }

    return source;
}
Amygdala answered 11/11, 2009 at 12:35 Comment(0)
J
5

It seems that when you call GetHBitmap() you are responsible for freeing the object

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

private void DoGetHbitmap() 
{
    Bitmap bm = new Bitmap("Image.jpg");
    IntPtr hBitmap = bm.GetHbitmap();

    DeleteObject(hBitmap);
}

I'm guessing that the BitmapSource doesn't take responsibility for freeing this object.

Jacobsen answered 11/11, 2009 at 12:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.