7

I have an application(WPF) which creates BitmapImages in huge numbers(like 25000). Seems like framework uses some internal logic so after creation there are approx 300 mb of memory consumed(150 virtual and 150 physical). These BitmapImages are added into Image object and they are added into Canvas. The problem is that when I release all those images memory isn't freed. How can I free memory back?

The application is simple: Xaml

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Canvas x:Name="canvas" Grid.ColumnSpan="2"></Canvas>
        <Button Content="Add" Grid.Row="1" Click="Button_Click"/>
        <Button Content="Remove" Grid.Row="1" Grid.Column="1" Click="Remove_click"/>
    </Grid>

Code-behind

        const int size = 25000;
        BitmapImage[] bimages = new BitmapImage[size];
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var paths = Directory.GetFiles(@"C:\Images", "*.jpg");
            for (int i = 0; i < size; i++)
            {
                bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length]));
                var image = new Image();
                image.Source = bimages[i];
                canvas.Children.Add(image);
                Canvas.SetLeft(image, i*10);
                Canvas.SetTop(image, i * 10);
            }
        }

        private void Remove_click(object sender, RoutedEventArgs e)
        {
            for (int i = 0; i < size; i++)
            {
                bimages[i] = null;
            }
            canvas.Children.Clear();
            bimages = null;
            GC.Collect();
            GC.Collect();
            GC.Collect();
        }

This is a screenshot of ResourceManager after adding images enter image description here

Blablablaster
  • 3,238
  • 3
  • 31
  • 33

5 Answers5

6

There was a bug in Wpf that we were bitten by where BitmapImage objects are not released unless you freeze them. https://www.jawahar.tech/home/finding-memory-leaks-in-wpf-based-applications was the original page where we discovered the issue. It should have been fixed in Wpf 3.5 sp1 but we were still seeing it in some situations. Try changing your code like this to see if that is the problem:

bimages[i] = new BitmapImage(new Uri(paths[i % paths.Length]));
bimages[i].Freeze();

We routinely freeze our BitmapImage objects now as we were seeing other instances in the profiler where Wpf was listening for events on the BitmapImage and thereby keeping the image alive.

If the Feeze() call isn't an obvious fix for your code, I would highly recommend using a profiler such as the RedGate Memory Profiler - that will trace a dependency tree that will show you what it is that is keeping your Image objects in memory.

fubaar
  • 2,489
  • 1
  • 21
  • 21
2

What worked for me was to:

  1. Set the Image control's ImageSource to null
  2. Run UpdateLayout() before removing the control that contains the Image from the UI.
  3. Make sure that you Freeze() the BitmapImage when you create it and that there were no non-weak references made to the BitmapImage objects used as ImageSources.

My cleanup method for each Image ended up being as simple as this:

img.Source = null;
UpdateLayout();

I was able to arrive at this through experimentation by keeping a list with a WeakReference() object pointing at every BitmapImage that I created and then checking the IsAlive field on the WeakReferences after they were supposed to be cleaned up in order to confirm that they'd actually been cleaned up.

So, my BitmapImage creation method looks like this:

var bi = new BitmapImage();
using (var fs = new FileStream(pic, FileMode.Open))
{
    bi.BeginInit();
    bi.CacheOption = BitmapCacheOption.OnLoad;
    bi.StreamSource = fs;
    bi.EndInit();
}
bi.Freeze();
weakreflist.Add(new WeakReference(bi));
return bi;
ivanatpr
  • 1,862
  • 14
  • 18
2

I followed the answer given by AAAA. Orignal code causing memory filled up is:

if (overlay != null) overlay.Dispose();
overlay = new Bitmap(backDrop);
Graphics g = Graphics.FromImage(overlay);

Inserted AAAA's code block, C# add "using System.Threading;" and VB add "Imports System.Threading":

if (overlay != null) overlay.Dispose();
//--------------------------------------------- code given by AAAA
Thread t = new Thread(new ThreadStart(delegate
{
    Thread.Sleep(500);
    GC.Collect();
}));
t.Start();
//-------------------------------------------- \code given by AAAA
overlay = new Bitmap(backDrop);
Graphics g = Graphics.FromImage(overlay);

Repeat looping this block now makes a steady and low memory footprint. This code worked using Visual Studio 2015 Community.

user3674642
  • 219
  • 3
  • 2
  • 1
    Is this answer for WPF or Winforms? I don't think WPF applications typically use the (GDI) `Graphics` and `Bitmap` classes. – jrh Jan 04 '17 at 15:53
1

This is still an array

BitmapImage[] bimages = new BitmapImage[size];

Arrays are continuous fixed-length data structures, once memory allocated for the whole array you cannot reclaim parts of it. Try using another data structures (like LinkedList<T>) or other more appropriate in your case

oleksii
  • 35,458
  • 16
  • 93
  • 163
  • 4
    The array will be size * pointer size, this doesn't take into account the heap space taken by objects being pointed at, which according to the sample above, have all been removed and dereferenced. So this statement, although accurate, won't explain why memory usage doesn't change after a garbage collection. – Adam Houldsworth Jun 26 '12 at 08:01
  • @AdamHouldsworth The array is reference by the variable `bimages`, stored on the local variable stack. When the method exits, this local variable pops out of scope, meaning that nothing is left to reference the array on the memory heap. The orphaned array then becomes eligible to be reclaimed by the GC. However, this collection may not happen immediately as the CLRs decision on whether to collect is based on a number of factors (available memory, the current allocation of memory etc.). This means that there is an indeterminate delay on the time taken before garbage collection. – MoonKnight Jun 26 '12 at 08:05
  • @Killercam `bimages` is not method local, it is a class member variable (or so I assume as it is used in both methods above). I agree that GC is indeterminate, but a `GC.Collect` is determinate - then it is just a discussion about what should be eligible. – Adam Houldsworth Jun 26 '12 at 08:06
  • The above includes explicit 'manual' calls to the GC. Calling the GC in this case will never guarantee immediate collection... – MoonKnight Jun 26 '12 at 08:07
  • 1
    @Killercam Explicit calls will always catch eligible items of the specified generation (or all if not specified). The CLR can actually make local variables eligible even before the method exits if the variable is no longer used or if it is manually `null`'d, but this is an optimisation and shouldn't be relied upon. – Adam Houldsworth Jun 26 '12 at 08:08
  • I suppose in this cas the OP might want to ensure the collection by waiting on any finalizers by calling `GC.WaitForPendingFinalizers()`, this might help the problem of deallocation... – MoonKnight Jun 26 '12 at 08:10
  • 1
    @Killercam Yes, my best-guess at this issue is that although it *appears* all items are fully de-referenced, there is something sly going on. Perhaps an item has got a hold of a reference in the WPF engine somewhere. – Adam Houldsworth Jun 26 '12 at 08:12
1

I am just telling my experience about reclaiming BitmapImage memory. I work with .Net Framework 4.5.
I create simple WPF Application and Load a large image file. I tried to clear Image from memory using following code:

private void ButtonImageRemove_Click(object sender, RoutedEventArgs e)
    {

        image1.Source = null;
        GC.Collect();
    }

But It didn't work. I tried other solutions too , but I didn't get my answer. After a few days struggling, I found out if I press the button twice, GC will free the memory. then I simply write this code to call GC collector a few second after clicking the button .

private void ButtonImageRemove_Click(object sender, RoutedEventArgs e)
    {

        image1.Source = null;
        System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate
        {
            System.Threading.Thread.Sleep(500);
            GC.Collect();
        }));
        thread.Start();

    }

this code just tested in DotNetFr 4.5. maybe you have to freeze BitmapImage object for lower .Net Framework .
Edit
This Code doesn't work unless layout get updated. I mean if parent control get removed, GC fails to reclaim it.

Ali Fattahian
  • 495
  • 1
  • 6
  • 24