Reproducing a WPF memory leak

time to read 7 min | 1382 words

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?