Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 346
Blablablaster Avatar asked Jun 26 '12 07:06

Blablablaster


4 Answers

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.

like image 137
fubaar Avatar answered Nov 07 '22 19:11

fubaar


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;
like image 43
ivanatpr Avatar answered Nov 07 '22 20:11

ivanatpr


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.

like image 2
user3674642 Avatar answered Nov 07 '22 19:11

user3674642


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

like image 1
oleksii Avatar answered Nov 07 '22 19:11

oleksii