Reproducing a WPF memory leak
I have run into WPF memory leaks before, and I thought that I fixed them all, but I started getting more reports from people experiencing large memory usage from NH Prof. That lead me to try to track it down again.
Short story, you can get the reproduction here: http://github.com/ayende/wpf-mem-leak
I left an NH Prof instance running overnight, and was able to confirm that there is a memory leak in there, just by seeing how much more memory it had in task manager. One of the nicest things in Windows 7 is the ability to generate a dump directly from task manager.
I loaded that dump into windbg, loaded sos and mscrowks and set out to figure out what was taking so much memory. The most common way to start a memory leak issue is to start figuring out what is taking all this memory. For that, the !dumpheap –stat command is great, since it give you memory usage by type and size. Here is what I found:
000007fee95b8a88 364674 11669568 MS.Utility.SingleItemList`1[[System.WeakReference, mscorlib]]
000007feeffe4748 51074 13161552 System.Object[]
000007fee77417a8 728189 17476536 MS.Internal.Data.ValueChangedEventArgs
000007fee95aafe8 364133 17478384 MS.Utility.ThreeItemList`1[[System.WeakReference, mscorlib]]
000007fee95b88c0 728814 17491536 MS.Utility.FrugalObjectList`1[[System.WeakReference, mscorlib]]
000007fee95b8838 728814 23322048 System.Windows.WeakEventManager+ListenerList
0000000000496170 19063 28004120 Free
000007fee7741730 728189 40778584 MS.Internal.Data.ValueChangedEventManager+ValueChangedRecord
000007fef0036c90 736210 47117440 System.EventHandler
000007feeffe7a90 1837702 58806464 System.WeakReference
I literally groaned when I saw that, based on my previous experience, it means that the memory leak is generated from inside WPF code, and that I am not likely to fix it. Then I set out to figure out where it is at. My suspicion fell on the statistics feature, it is the only thing that really updates frequently, after all.
So I setup to build a reproduction. Sadly, it is trivially easy to reproduce the issue. Given this XAML:
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=Statistics}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Padding="2 0 0 0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Key}"
ToolTip="{Binding Path=Key}"
TextTrimming="CharacterEllipsis" />
<GridSplitter Grid.Column="1"
Background="#3000"
Width="2"
Margin="2 0" />
<TextBlock Text="{Binding Path=Value}"
Grid.Column="2" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
The following code will generate a memory leak:
public partial class Window1 : Window
{
readonly DispatcherTimer timer = new DispatcherTimer(DispatcherPriority.Normal)
{
Interval = TimeSpan.FromMilliseconds(1)
};
private readonly MyModel context;
public Window1()
{
InitializeComponent();
context = new MyModel();
context.UpdateData();
timer.Tick += (sender, args) => context.UpdateData();
DataContext = context;
timer.Start();
}
public class MyModel
{
public ObservableCollection<DictionaryEntry> Statistics { get; set; }
public MyModel()
{
Statistics = new ObservableCollection<DictionaryEntry>();
}
public void UpdateData()
{
Statistics.Clear();
Statistics.Add(new DictionaryEntry("Time", DateTime.Now.ToString()));
Statistics.Add(new DictionaryEntry("Mem", GC.GetTotalMemory(true).ToString("#,#")));
}
}
}
When you run the code, you can see that memory usage is going up all the time. As far as I can tell, I am not doing anything that is wrong or even slightly strange here. You can get the reproduction here: http://github.com/ayende/wpf-mem-leak
I tried several workarounds so far, but I didn’t like any of them. Any ideas?
Comments
I think Clear (istics.Clear();) may take a while to clean
maybe. we can re-initialize Statistics
public void UpdateData() {
<dictionaryentry();
Hi Ayende,
The problem here is that you use a TwoWay binding to an object that doesn't provide change notification. This forces WPF to use other methods to synchronize the values.
There are two solutions:
1) In your bindings use Mode=OneWay. Since you are recreating the items in each iteration you need to read the value only once.
2) Instead of DictionaryEntry use a custom class that implements INotifyPropertyChange interface. This way WPF is able to track changes to property values easily.
Here is kb article that i think describes your situation:
http://support.microsoft.com/kb/938416/en-us
Hi Szymon,
Please correct me if I
I tried changing like this below but it still keep increasing.
<itemscontrol
<itemscontrol.itemtemplate
<datatemplate
<border
<grid
<grid.columndefinitions
<columndefinition
<columndefinition
<columndefinition
<gridsplitter
Background="#3000"
<textblock
Grid.Column="2" />
public class MainViewModel : ViewModelBase
<model();
<model Statistics { get; set; }
Am I missing something?
Hi Ayende, i solved Memory Leak, take a look please:
cid-b27ccfaa07b93be8.skydrive.live.com/.../....rar
What the hell is MS.Utility.FrugalObjectList?
Ayende, as you can see in my code i changed you DataContext object to:
public class MyModel: INotifyPropertyChanged
And in WPF Window, i chaged to:
<stackpanel
<stackpanel
<textblock
<textblock
If you run, you will see memory is not getting high by time pass... Memory leak is gone :)
(sorry for my bad english)
Reading posts / comments like this make me glad I'm not developing desktop applications.
There are just some things you can appreciate people knowing, that you have no desire to know yourself. This kind of non-portable knowledge being one such case :)
Does anyone have an easy way of doing such a clean memory dump in WinXP? I tried using Debugging Tools for Windows, but WinDbg keeps complaining I am missing symbols. (I followed the installation instructions on MSDN).
I haven't looked at Paulo's solution, so if his works, that's great.
In the last blog posting about this issue I believe I posted a "hackish" solution to the problem that should solve your problem. It involves calling the GC and collapsing the process memory. I'm in no way saying it's an ideal solution, but works in a pinch if everything else fails.
@Eyston
Between cross browser css hacks, javascript which behaves differently among different browsers and even browser versions, having to support every platform under the sun only to have to rebase your code every time a new version of a browser comes out...
boy I'm glad I develop desktop applications. :)
Also, BTW, WPF is the newest UI technology...Winforms (AFAIK) is much more stable since its approaching a decade in maturity
Mode=OneTime
or
Mode=OneWay
On those bindings.
This looks really bad to me. I think you should open a ticket about it at connect.microsoft.com. Because from my understanding of your case, it would mean almost any binding is leaking.
As someone wrote, there's a KB article describing a very similar leak here: http://support.microsoft.com/kb/938416. But in this KB there's an additional condition: the databinding source object X must hold a reference to the target of the binding. Which you don't have.
Your repro seems to imply that:
Binding to a plain CLR object (no DependencyProperty and no INotifyPropertyChanged) without specifying Mode=OneTime always creates a memory leak.
And there are tons of those in every WPF app...
@jdu,
did you download my code? See, in my version, implementing INotifyPropertyChanged resolved memory leak...
Comment preview