tags:

views:

520

answers:

9

This is a C# WinForm question. I have a MDI child form, when the form is opened, it will generate many tables to display in a DataGridView. The tables can be big and I use DataBinding to connect the tables to the DataGridView. When I close the form, I notice that the memory taken by the form is reclaimed timely.

I use the normal way of showing the MDI child form as:

var f = new FormBigMemory(objPassedIn);
f.Show();

As shown here, I cannot explicitly call the Dispose() method of the form. I assume that when f is out of its life circle, .net will automatically reclaim the memory it takes.

Since the form takes a lot memory, I want to explicitly call GC.Collect(); (I know it may not be a good idea to do so). In order to do this, I change the code to show the form in dialog model using the following code:

using(var f = new FormBigMemoryDialog(objPassedIn);
{
    f.ShowDialog();
}

GC.Collect();

I am very disappointed to see that the memory taken by the form isn't reclaimed after GC.Collect() is called. I use memory profiler tool to get the snapshot of the memory after the form is closed and GC.Collect() is called. The biggest memory is held up by a BindingList! I really don't understand: if the whole form is disposed, why the BindingList still exists in the memory. I have checked my code and there is no where the BindingList reference is leaked out of the form.

Any idea why this wired thing happens to me? Many thanks to any suggestion and tips on .net memory management.

+3  A: 

Never call GC.Collect unless you have a very good reason to do so.

I don't know the details, but this is what I know:

  • Something is removed after all references are removed.
  • The GC won't try to remove all stuff at once, it doesn't want to disturb the program from running.
  • There is a good chance the form is just hidden, instead of closed. How do you close the form?
  • As Mitch Wheat says, Event Handlers can be stubborn, as you often forget to remove them.
  • Dispose does not make the form being removed from memory, it calls a plain method inside the form, maybe you can insert your own cleanup logic inside it.
Dykam
I wonder if http://msdn.microsoft.com/en-us/library/yh598w02%28VS.71%29.aspx adds any light to the problem of dispose and memory management.
Andrew Weir
+2  A: 

Are you sure that you are unwiring all event handlers that the form uses?

Update in response to question in comments: Whenever an object adds an event handler to an event (+= EventHandler) there should be a corresponding (-= EventHandler) when you are finished with the object instance. Otherwise, it will act as a root object in the heap, and garbage collection will not reclaim.

Mitch Wheat
This is one of the common reasons,but easy to neglect, why forms or controls are still alive even though you thought they should be already gone.
Chansik Im
What do you mean by unwiring event handlers? Is there any example code for this? I don't believe any control will still exist after the form is closed given that the control is not shared across many places, which is my case.
Steve
Unwiring events is only necessary for certain circumstance. I think in most of the cases, such as anonymous button click event, does not require the unwiring. Because such event and the form are circular references to each other and garbage collector is robust enough to handle circular references like this.Only those non-circular references need to be unwired and this only helps if the subscriber lives longer than the publisher. I have difficulties in identifying such event handler references.But a good point anyway. Thanks. I am also suspecting the data bindings should also be cleared.
Steve
A: 

The only thing I do differently to you is the use of var when creating the new form. I would have used the class name like a normal definition.

FormBigMemory f = new FormBigMemory(objPassedIn);
f.MdiParent = this;
f.Show();

Is there a possibility that by using var (which is only really intended for generics by my understanding) that you're somehow keeping the memory available? I don't quite understand how that would happen but it might be worth a google search.

Or perhaps by not defining the MdiParent you're keeping the form alive in memory?

If anyone else could shed light on my ideas, it would be nice. Try to solve this guys problem.

Andrew Weir
Var is just an keyword which makes the compiler determine the type at compile time by time inference. The resulting code would be the same.
Dykam
I agree with you Dykam. But you're still giving the compiler some work to do which has specific use with generics. I'm just wondering if something regarding the keyword is used later in memory.Just poking and prodding for answers. :)
Andrew Weir
var was introduced to the language primarily to support anonymous types. It also makes it easier to type code to instantiate a generic type, especially ones that have nested type arguments. That said, that's merely a convenience - even non-generic long type names benefit from that.There is, however, no difference in the generated code. var is purely a compile time entity.
Senthil Kumar
A: 

Why do you expect Form.Dispose() to clean up BindingList? If you're talking about BindingList from System.ComponentModel, it is not even disposable. I am not so sure how deep Form goes about disposing its resources. It may iterate through its Controls collections and call Dispose on controls that implement IDisposable. However, if you define a field on a Form such as DataSet or List and populate it with lots of data, calling Dispose on that Form will have no effect on the memory occupied by that field. If you want that field to be GCed, then you just need to make sure it is just not referenced from anywhere

How do you use objPassedIn? Is it possible that this object is keeping references around and thus preventing GC from collect them? I also recommend cleaning objects explicitly in FormClosing event by setting them to null and/or calling Dispose on them. As @Mitch mentioned, not unsubscribing from events on objects that have a longer lifetime than your form could also be the culprit as to why GC is not collecting the objects that you're expecting. Remember you can call Dispose on a Form but as long as you have a reference to the form, it will live on and not garbage collected.

TestForm frm = new TestForm();
frm.MyTextField = "Hello World";
frm.ShowDialog();
frm.Dispose();
string textField = frm.MyTextField;

This would still work.Imagine instead of a simple text field, you have List with thousands of objects. You will have a problem however if you try to show that form again because all of the UI related resources that the form needs for proper display are disposed.

Also, assuming that you're not keeping references around, you should not need to force a garbage collection explicitly. GC will occur whenever there's a memory pressure by itself. Just let it happen naturally. If you're keeping references around which appears to be the problem in your case, it does not matter how many times you call GC, it will not collect the things you expect because they're still referenced.

Mehmet Aras
A: 

Doesn't the memory profiler tell you what's holding the BindingList? If it doesn't, this answer might be useful.

Senthil Kumar
A: 

Don't expect .NET to collect your memory right away.

Read this and this to get a better picture on how .NET is handling the GC.

And look at this to get a better idea on how to troubleshoot your problem. Especially read the What If Objects Survive section.

Jimmy Chandra
A: 

The garbage collector runs in its own time.

A WinForm and a DataGridView are separate, distinct objects.

A BindingList is a part of the DataGridView and has nothing to do with the Form.

You need to dispose of the DataGridView when the WinForm holding the DataGridView is closed.

DataGridView.Dispose() should be called in the appropiate place in your Form, probably the FormClosing event.

A: 

Thanks for many answers. Let me clear some points.

  1. I don't mean to dispose the BindingList. A BindingList is used as an array in my code so I would expect to see the reclaim of the memory held by the BindingList. My bad not to say clearly.

  2. I understand not to call the GC action myself. But in my case, I want to explicitly see the reclaim of memory held up by the form immediately after the form is closed, anyway to do that? Example code?

  3. In my problem, I can repeatedly open the same form multiple times and each time I close the form, the memory consumption increases, until the OutOfMemory throws. This is obviously not right. What I expect to see is, after the form is closed, the memory drops back to the original level and I can repeatedly open the form and close it without increasing the memory (dramatically).

Any suggestion? Thanks.

Steve
A: 

I further investigated the code. The biggest array (a BindingList) is not released after the form is closed. It is referred by a BindingSource and the BindingSource is referred by EventHandler, CurrencyManager, and a user control. The BindingSource and the user control are both referred by the (closed) form and the (closed) form is referred by some event handlers.

The above is the memory snapshot after I closed the form. I use dialog model to open the form and after it is closed, its Dispose() method is called and after that I also call GC.Collect() to force the memory to be reclaimed. Why after this the form instance still exists in my memory snapshot??

Steve
This may be because the event handlers are still attached to the events. An event is a list of delegate instances. A delegate instance refers to the handler, but also to the instance of the class for which the handler will be invoked (in this case, the form).
John Saunders