Binding to list causes memory leak
Solution 1:
Ahhh got you. Now I understand what you mean.
You set the Content to null and so you kill the compelte ListBox but still the ItemsSource binds to List and so ListBox memory is not completely released.
That is unfortunately a well known issue and also well documented on MSDN.
If you are not binding to a DependencyProperty or a object that implements INotifyPropertyChanged or ObservableCollection then the binding can leak memory, and you will have to unbind when you are done.
This is because if the object is not a DependencyProperty or does not implement INotifyPropertyChanged or not implementing INotifyCollectionChanged (Normal list is not implementing this) then it uses the ValueChanged event via the PropertyDescriptors AddValueChanged method. This causes the CLR to create a strong reference from the PropertyDescriptor to the object and in most cases the CLR will keep a reference to the PropertyDescriptor in a global table.
Because the binding must continue to listen for changes. This behavior keeps the reference alive between the PropertyDescriptor and the object as the target remains in use. This can cause a memory leak in the object and any object to which the object refers.
The question is...is Person implementing INotifyPropertyChanged?
Solution 2:
That's an old post, I see. But the explanations provided especially by the accepted answer is not very accurate and its implications are wrong.
Abstract
Beforehand, this is not a real memory leak. The special binding engine's lifetime management for collections that do not implement INotifyCollectionChanged
and their associated CollectionView
takes proper care of the allocated memory.
WPF supports binding to many different types like DataTable
and XML or in general to types that implement IList
, IEnumerable
or IListSource
. If this was a serious bug, then all those bindings would be dangerous.
Microsoft would propagate warnings in their docs against e.g., binding to DataTable
as they do in case of potential memory leaks in context with events or data binding.
It is indeed true that this special behavior can be avoided when binding to a collection of type INotifyCollectionChanged
- or by avoiding creating a CollectionView
for a collection that does not implement INotifyCollectionChanged
:
the observed behavior is actually induced by the actual CollectionView
management of the binding engine and not the data binding itself.
The following code triggers the same behavior as would do a binding to a List\<T>
:
var list = new List<int> {1, 2, 3};
ICollectionView listView = CollectionViewSource.GetDefaultView(list);
list = null;
listView = null;
for (int i = 0; i < 4; i++)
{
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
}
Result: the entire collection reference graph and the CollectionView
are still in memory (see explanation below).
This should be proof that the behavior is not introduced by data binding, but the binding engine's CollectionView
management.
Memory Leaks in Context of Data binding
The memory leak issue regarding data binding is not related to the type of the property, but to the notification system that the binding source implements.
The source must either
a) participate in the dependency property system (by extending DependencyObject
and by implementing properties as DependencyProperty
) or
b) implement INotifyPropertyChanged
Otherwise, the binding engine will create a static reference to the source. A static reference are root references. Due to their nature to be reachable during the lifetime of the application, such root references, like static fields and every object (memory) they reference, will never be eligible for garbage collection and thus create the memory leak.
Collections and CollectionView
Management
Collections are a different story. The cause of the alledged leak is not the data binding itself. It's the binding engine that is also responsible for creating the CollectionView
of the actual collections.
Whether the CollectionView
is created in context of a binding or when calling CollectionViewSource.GetDefaultView
: it's the binding engine that creates and manages the views.
The relationship between collection and CollectionView
is an unidirectional dependency, where the CollectionView
knows the collection in order to synchronize itself, while the collection does not know the CollectionView
.
Every existing CollectionView
is managed by the ViewManager
, which is part of the binding engine. To improve performance, the view manager caches views: it stores them in a ViewTable
using WeakReference
to allow them to be garbage collected.
When a collection implements INotifyCollectionChanged
│══════ strong reference R1.1 via event handler ═══════▶⎹
Collection │ │ CollectionView
│◀═══ strong reference R1.2 for lifetime management ═══⎹ ̲ ̲
△
│
│
ViewTable │───── weak reference W1 ──────┘
The CollectionView
itself is target of a strong reference R1.1 from the underlying source collection if this collection implements INotifyCollectionChanged
.
This strong reference R1.1 is created by the CollectionView
the moment it observes the INotifyCollectionChanged.CollectionChanged
event (by attaching an event callback that the collection stores in order to invoke it when raising the event).
This way, the lifetime of the CollectionView
is coupled to the lifetime of the collection: even if the application has no references to a CollectionView
, because of this strong references the lifetime of the CollectionView
is extended until the collection itself is eligible for garbage collection.
Since the CollectionView
instances are stored in the ViewTable
as WeakReference
W1, this lifetime coupling prevents the WeakReference
W1 from getting garbage collected prematurely.
In other words, this strong coupling R1.1 prevents the CollectionView
from being garbage collected before the collection.
Additionally, the manager must also guarantee that as long the CollectionView
is referenced by the application, the underlying collection continues to exist, even if this collection is no longer referenced. This is achieved by keeping a strong reference R1.2 from CollectionView
to the source collection.
This reference always exists, no matter the collection type.
When a collection does not implement INotifyCollectionChanged
Collection │◀═══ strong reference R2.1 for lifetime management ════│ CollectionView
̲ ̲
▲
║
║
ViewTable │════ strong reference R2.2 ═════╝
Now, when the collection does not implement INotifyCollectionChanged
, then the required strong reference from collection to CollectionView
does not exist (because no event handlers are involved) and the WeakReference
stored in the ViewTable
to the CollectionView
could be potentially garbage collected prematurely.
To fix this, the view manager must keep the CollectionView
"artificially" alive.
It does this by storing a strong reference R2.2 to the CollectionView
. At this moment the view manager has stored a strong reference R2.2 to the CollectionView
(due to the lack of INotifyCollectionChanged
) while this CollectionView
has a strong reference R2.1 to the underlying collection.
This results in the view manager keeping the CollectionView
alive (R2.2) and therefore the CollectionView
keeps the underlying collection alive (R2.1): this is the cause for the perceived memory leak.
But this is not a real leak, as the view manager controls the lifetime of the strong reference R2.2 to the CollectionView
by registering the strong reference R2.2 with an expiration date. This date is renewed on each access to the CollectionView
.
The view manager will now occasionally purge those references when their expiration date is expired. Finally, those references will get collected when
the CollectionView
is not referenced by the application (ensured by the garbage collector) and the underlying collection is no longer referenced (ensured by the garbage collector).
This behavior is introduced to allow the strong reference R2.2 while avoiding a leak.
Conclusion
Due to the special lifetime management (using expiration dates) for a CollectionView
of a collection that does not implement INotifyCollectionChanged
, the CollectionView
is kept alive (in memory) much longer. And because the CollectionView
in general has a strong reference to its source collection, this collection and its items and all reachable references are also kept alive much longer.
If the collection had implemented INotifyCollectionChanged
, then the view manager would not have stored the strong reference to the CollectionView
and therefore the CollectionView
would have been garbage collected the moment it is no longer referenced and the source collection became unreachable.
The important point is, the lifetime of the strong reference to the CollectionView
is managed by the ViewManager
i.e. binding engine. Due to the management algorithm (the expiration date and the occasional purge), this lifetime is significantly extended.
Therefore, the observation of the persisting allocated memory after all references to the collection and its views have been destroyed is deceiving. It is not a real memory leak.