Blablablaster
Blablablaster

Reputation: 3348

Garbage collection fails to reclaim BitmapImage?

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

Upvotes: 7

Views: 7146

Answers (5)

fubaar
fubaar

Reputation: 2519

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.

Upvotes: 6

user3674642
user3674642

Reputation: 219

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.

Upvotes: 2

Ali Fattahian
Ali Fattahian

Reputation: 495

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.

Upvotes: 1

ivanatpr
ivanatpr

Reputation: 1880

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;

Upvotes: 2

oleksii
oleksii

Reputation: 35935

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

Upvotes: 1

Related Questions