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?

Print | posted on Tuesday, November 17, 2009 10:56 AM

Feedback


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 11:12 AM Michael Sync

I think Clear (istics.Clear();) may take a while to clean

maybe. we can re-initialize Statistics

public void UpdateData() {
Statistics = new ObservableCollection();
Statistics.Add(new DictionaryEntry("Time", DateTime.Now.ToString()));
Statistics.Add(new DictionaryEntry("Mem", GC.GetTotalMemory(true).ToString("#,#")));
}


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 11:16 AM Szymon

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.


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 11:20 AM grega_g

Here is kb article that i think describes your situation:
http://support.microsoft.com/kb/938416/en-us


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 11:24 AM Michael Sync

Hi Szymon,

Please correct me if I
I tried changing like this below but it still keep increasing.

1.











ToolTip="{Binding Path=Key,Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
Background="#3000"
Width="2"
Margin="2 0" />
Grid.Column="2" />






2.

public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Statistics = new ObservableCollection();
}

public void UpdateData() {
Statistics.Clear();
Statistics.Add(new Model("Time", DateTime.Now.ToString()));
Statistics.Add(new Model("Mem", GC.GetTotalMemory(true).ToString("#,#")));
}

public ObservableCollection Statistics { get; set; }
}

public class Model : ViewModelBase
{
public Model(string str1, string str2)
{
key = str1;
val = str2;
}

private string key = string.Empty;
public string Key
{
get { return key; }
set {
key = value;
this.OnPropertyChanged("Key");
}
}

private string val = string.Empty;
public string Value
{
get { return val; }
set
{
val = value;
this.OnPropertyChanged("Value");
}
}

}

Am I missing something?


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 12:06 PM Paulo Quicoli

Hi Ayende, i solved Memory Leak, take a look please:

cid-b27ccfaa07b93be8.skydrive.live.com/.../....rar


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 12:52 PM Rafal

What the hell is MS.Utility.FrugalObjectList?


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 1:02 PM Paulo Quicoli

Ayende, as you can see in my code i changed you DataContext object to:

public class MyModel: INotifyPropertyChanged
{
private string runningTime;
public string RunningTime
{
get { return runningTime; }
set
{
runningTime = value;
OnPropertyChanged("RunningTime");
}
}

private string memUsed;
public string MemUsed
{
get { return memUsed; }
set
{
memUsed = value;
OnPropertyChanged(MemUsed);
}
}

public void UpdateData()
{
RunningTime = DateTime.Now.ToString();
MemUsed = GC.GetTotalMemory(true).ToString("#,#");
}

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}

And in WPF Window, i chaged to:












If you run, you will see memory is not getting high by time pass... Memory leak is gone :)
(sorry for my bad english)


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 4:55 PM Eyston

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 :)


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 7:10 PM Graham Nash

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).


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 7:11 PM Andrew

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.


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 7:54 PM J

@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


Gravatar

# re: Reproducing a WPF memory leak 11/17/2009 10:19 PM Justin Chase

Mode=OneTime

or

Mode=OneWay

On those bindings.


Gravatar

# re: Reproducing a WPF memory leak 11/19/2009 12:18 PM jdu

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...


Gravatar

# re: Reproducing a WPF memory leak 11/19/2009 8:19 PM Paulo Quicoli

@jdu,

did you download my code? See, in my version, implementing INotifyPropertyChanged resolved memory leak...

Comments have been closed on this topic.