Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding to list causes memory leak

When I bind an ItemsSource of a ListBox to a List the binding engine holds on to the list elements after the control is gone. This causes all the list elements to stay in memory. The problem goes away when using an ObservalbleCollection. Why does this happen?

The xaml inside the window tag

<Grid>
    <StackPanel>
        <ContentControl Name="ContentControl">
            <ListBox ItemsSource="{Binding List, Mode=TwoWay}" DisplayMemberPath="Name"/>
        </ContentControl>
        <Button Click="Button_Click">GC</Button>
    </StackPanel>
</Grid>

Code behind:

public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }

private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.DataContext = null;
        ContentControl.Content = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

ViewModel

class ViewModel : INotifyPropertyChanged
{
    //Implementation of INotifyPropertyChanged ...

    //Introducing ObservableCollection as type resolves the problem
    private IEnumerable<Person> _list = 
            new List<Person> { new Person { Name = "one" }, new Person { Name = "two" } };

    public IEnumerable<Person> List
    {
        get { return _list; }
        set
        {
            _list = value;
            RaisePropertyChanged("List");
        }
    }

class Person
{
    public string Name { get; set; }
}

Edit: To check the leaking of the person istances, I used ANTS and .Net memory profiler. Both show that after pushing the GC-button only the binding engine is holding reference to the person objects.

like image 795
user1182735 Avatar asked Oct 22 '13 07:10

user1182735


People also ask

What is the main cause of memory leaks?

DEFINITION A memory leak is the gradual deterioration of system performance that occurs over time as the result of the fragmentation of a computer's RAM due to poorly designed or programmed applications that fail to free up memory segments when they are no longer needed.

Does malloc cause memory leak?

At the malloc() , new memory is allocated, but it is never assigned to a pointer. There is no way to keep track of the memory and no way to deallocate it; thus, we have a memory leak.

What should be avoided to prevent memory leaks?

Causes of Memory Leaks and Their Solutions One should not use static views while developing the application, as static views are never destroyed. One should never use the Context as static, because that context will be available through the life of the application, and will not be restricted to the particular activity.

How do you identify a memory leak?

Running out of memory is the simplest way to identify a memory leak, and it's also the most common approach to uncovering one. That's also the most inconvenient way to find a leak. You'll probably notice your system slowing down before you run out of RAM and crash your application.


2 Answers

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?

like image 173
dev hedgehog Avatar answered Oct 26 '22 09:10

dev hedgehog


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.

like image 26
BionicCode Avatar answered Oct 26 '22 07:10

BionicCode