Does the following XAML potentialy leak memory?
I'm using the .NET4 WPF DataGrid to show a SQL table containing lots of images.
The XAML code in question is:
...
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Converter={StaticResource ImageConverter}, Path=Picture}" Stretch="Uniform" MaxHeight="200" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
...
And the ImageConverter is written as such:
[ValueConversion(typeof(Binary), typeof(BitmapImage))]
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = new System.IO.MemoryStream((value as Binary).ToArray());
bi.EndInit();
if (bi.CanFreeze)开发者_Python百科 bi.Freeze();
return bi;
}
else return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
My question is does the code above leak memory?
I have tried to do some profiling about this, but I'm not sure if I'm interpreting the results correctly.
First of all, the SQL table containing the images uses 38MB of disk space (the images should be stored in png format). After loading all the images to the datagrid via LINQ, the app uses some 170+ MB extra RAM. This can probably be contributed to uncompressing the images and the fact that the wpf datagrid is a gigantic memory hog even when virtualizing is enabled. After closing the window the memory usage does not drop. Reopening the window causes another 170+ MB of RAM to be used, making the total memory usage roughly 400MB. If I reopen the window again, then the memory usage drops to about 330MB. Reopening the window again takes the memory usage to 380MB. Reopening again takes it to 270MB. Reopening again takes it to 426MB. So as you can see very fluctuating...
This little bit of testing was done with Win7 x64 and 8GB of RAM (the app is compiled with the Any CPU option).
I did try the same test on a virtual xp machine with <512MB of ram (about 384 if I remember correctly). And everytime I reopened the window I first saw a dramatic drop in memory usage when the window was being loaded and then the same amout of memory reused, so the overall usage was about the same.
I interpret those results such that the GC doesn't bother cleaning up unless there is high pressure on the memory subsystem. But in that case calling GC.Collect() after I close the window should release the majority of the memory? Only that I didn't, I saw only a drop of a 4-6MB in memory usage when I called GC.Collect (I also tried all the possible parameters, including forcing collection on all generations and calling GC.WaitForPendingFinalizers()).
This got me thinking that maybe the unused leaking data is being pushed into the page file. But the page file usage drops and rises acording to the memory usage equaly.
All things considered there ought not to be a memory leak here, but I can't get the memory usage to come down after I have closed the window. I did try to attach some .NET profiler to my process, but thoses profilers are so complex that I can't quite figure out whether the shown image objects are still alive and referenced by something. Or are they dead and the GC simply hasn't cleaned them up...
The way you can find out whether or not the memory is written to the page file is through GC.GetTotalMemory(true)
. This should return the actual memory consumption of the .NET process including any pages that may have been written to the page file.
This may even be different from e.g. the memory reported by the task manager, but it will report what has actually been allocated by .NET objects.
See http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/2ebbff38-f881-47ad-a4b4-9c9157fb3b5b for more information concerning the difference in memory consumption reported by the task manager and GetTotalMemory
.
My guess is that GC.GetTotalMemory(true)
will report that all memory has actually been released after the collection.
In my opinion there is no memory leak in that code. The fact that on the virtual machine memory drops almost immediately means that the GC is doing its job correctly.
One thing I found helpful in a similar situation is to put a button somewhere that calls GC.Collect, and hit it several times. Usually after ~3 times dead objects are "GCollected", if it's not the case you probably have a leak.
the memoryleak problem with DataGridTemplateColumn seems to be gone with .NET 4.5
精彩评论