
In a comment on part 2 about the photoviewer application Erno pointed out a problem with my photoviewer application. It uses some serious memory and it doesn’t really clean up after itself. This already caused some frowns on my end with an album containing 120 images on a 4GB machine, so you can imaging I took this comment rather serious.
Luckely he pointed me to some resources that could help fix the problem. In this article I want to show you what steps I took to locate the problem and fix it with some clever C# code. There is actually a good reason for using the amount of memory it is using now so I will also take a moment to explain why the application is actually eating all this memory.
The problem
In short the photoviewer application is eating a lot of memory and doesn’t return memory when it doesn’t need it any longer. The area where this is actually a problem is limited to the rendering of thumbnails and rendering the full version of images.
The application takes up 45MB with only 12 thumbnails loaded and it slowly grows towards 100MB or more. This may not sound like much, but you can imagine that this becomes a problem when you add more photos.
The memory usage doesn’t grow steadily over time, but grows as soon as you select another album from the dropdown. However switching back to a previous album doesn’t cause the application to use more memory which is a good thing.
Possible leaky spots
Before I start talking about a possible solution to the memory problem I want to show some pointers that I found to be useful in my particular case. (Thanks Erno for the valuable links, they helped a great deal identifying the actual problem).
The photoviewer application contains a few possible leaky spots, here’s a list of them:
- In WPF version 3.5 without SP1 there seems to be a problem with bitmap images not being cleaned up. This only happens when you do not freeze the bitmap image after you initialized it. Photoviewer makes extensive use of BitmapImage, but is compiled against WPF 3.5 SP1, so that problem should not occur. It is worth checking out nonetheless, because freezing the images can improve performance even if it doesn’t cause a leak.
- The garbage collector never cleans up statically allocated data. So each static member in a class that gets initialized never gets collected by the garbage collector and lives until the application is closed. It’s good to remember this because people often forget about this one. I did too and as a result the ImageCache used in the application could well cause a problem in this area.
How photoviewer loads images
The first possible leaky spot I investigated is the image loading logic of the application. Instead of loading images synchronously I opted to load the images required by the user interface on a background thread. This makes the application a whole lot more response as loading 30 thumbnails can take a while.
The loading logic for the thumbnails looks like this:
public ImageSource Thumbnail
{
Get
{
if(_retrievingThumbnail)
return null;
_thumbnail = ImageCache.GetCachedImageSource(_original.Path + “_thumbnail”);
if (_thumbnail == null)
{
_retrievingThumbnail = true;
// Load the thumbnail in a background thread
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
{
BitmapImage img = new BitmapImage();
img.BeginInit();
img.StreamSource = _original.Load();
img.DecodePixelWidth = 120;
img.CacheOption = BitmapCacheOption.OnLoad;
img.EndInit();
img.Freeze();
_thumbnail = img;
_retrievingThumbnail = false;
ImageCache.SaveImageSourceInCache(_original.Path + “_thumbnail”,img);
// Notify the UI that the thumbnail has been updated
OnPropertyChanged(new PropertyChangedEventArgs(“Thumbnail”));
}),DispatcherPriority.Background);
// Return an empty thumbnail as placeholder
return null ;
}
return _thumbnail;
}
}
As you can see the image gets loaded and the application freezes the image right after it is done loading it. So this leaky spot doesn’t actually leak and shouldn’t cause me any problems. The only possible issue here is that I didn’t lock the image before changing it, which may cause some weird race condition. But since I’m still working on a beta version this can be fixed at a later moment.
The role of the image cache
In the early stages of developing the photoviewer application I noticed a drastic drop in perceived performance when switching albums. This was partly caused by the fact that I had my photos stored on a network attached storage device. However it was also caused by the image loading logic. Each time I switched albums all the images had to be loaded again on a background thread. And when I switched back to an album that I had viewed before the loading performance was still very poor.
To fix this I introduced the ImageCache into the application. This keeps track of all thumbnails and full size images that were loaded before. By doing this the performance went up with 100% which made me happy. However this performance boost also has a downside. The memory usage of this class is huge! It’s leaking memory all over the place because the images are not kept in a normal cache, but are kept in a statically allocated dictionary.
It looks like I am facing a trade-off here. I could remove the image cache which makes the application use a whole lot less memory. However that will make the application feel slow again, which I don’t really want to happen.
Fixing the memory leak – attempt 1
I tried two possible solutions for the memory leak, where the first attempt is clearly not the solution, but rather an exploration of what would happen if I changed the behavior of the image cache.
What I did was change the image cache in such a way that it stores a WeakReference to the various images instead of a strong reference which I used before. This fixes the problem; the garbage collector collects the images as soon as I switch albums. The effect of this is that the memory usage doesn’t grow, but always stays within an acceptable range.
Fixing the memory leak – attempt 2
I could go with the standard ASP.NET cache to store the images in memory. This however requires me to use the HttpRuntime, which I don’t think is a really good idea inside a WPF application. My instincts tell me that I want to go there.
The really good solution in my case is to limit the number of cached images to say a number of 10 and let the user determine if he wants more through configuration. It still makes the application use more memory than it needs to actually display the images, but that’s a trade-off I am willing to make for the speed I am getting out of it.
Conclusion
Having a huge amount of memory is nice, but it doesn’t mean developers shouldn’t worry about memory usage. The same goes for having a garbage collector. Some things still make the application leak memory and I can’t really say that the garbage collection mechanics made it any easier to locate and fix these problems.
The key to keeping your application from leaking memory is to know how the garbage collection mechanism works and build your application in such a way that you don’t block the garbage collector when you don’t need to.
And as for the memory usage, know when to drop stuff into the garbage bin. It’s not really necessary to keep 250 images in memory when you are only showing 10% of these images.
I hope this article gives some insight in what can actually cause a memory leak and what it takes to fix memory leaks.
Resources
For this article I used the following resources:
- http://blogs.msdn.com/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx
- http://msdn.microsoft.com/en-us/library/ms404247.aspx
P.S. For the observant reader: Did I fix a memory leak, or was this actually a performance characteristic that I bend a little too much out of shape?