Reputation: 3348
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
Upvotes: 7
Views: 7146
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
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
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
Reputation: 1880
What worked for me was to:
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
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