Pete, thank you for your detailed response. From your words I guess I could
not make myself perfectly clear.
Of course I know, that calling the Dispose method of an object does not mean
killing references to this object. So, what am I doing:
In the initialization method of one of my classes the following assertion to
a member variable (MyForm m_form) is made:
m_myForm = new MyForm();
m_myForm.Disposed += new EventHandler(OnMyFormDisposed);
m_myForm.Show();
m_myForm is the only reference to the MyForm instance that _I_ am creating
and holding.
In OnMyFormDisposed I drop the reference
m_myForm = null;
I assumed, that now the form was disposed (what else should be the reason to
fire the Disposed event) and all of my selfmade references to the form were
dropped. I concluded that now the GC should have the possibility to clean up
and free resources.
From this point I profiled the memory consumption of my application and saw,
that the GC never took care of MyForm instances. I looked at the GC paths and
realized that there was still at least one reference to the object. My
profiler says that "control of
System.Windows.Forms.Control/ControlNativeWindow" is still pointing at my
object. I assume that this has something to do with the controls which I
place on the form: a ToolStrip with some ToolStripButtons and a TabControl
containing a ListView.
With trial-and-error I meanwhile discovered that the form is only garbage
collected if I call this.Dispose() inside a Form.Closing event handler AND I
set the AllowItemReorder property of the contained ToolStrip to false. Why
does this work? Frankly, I don't know.
Cheers
David.
My understanding is that .Dispose() does not get called automatically by the
GC. Maybe you are confusing it with a Finalizer?
The IDispose.Dispose() interface is supposed to get rid of any unmananged
resources and references that could unitentionally keep objects alive (event
handlers etc). Some framework components I've noticed will when they are
done with an object call .Dispose() if it implements this interface, but in
general you have to call it yourself.
In your case the toolstrip probably has a reference to the root for
responding to an event of somekind, setting the property likely removes the
event also the toolstrip.dispose() should get rid of that. I would first
make sure that it is added to the Form.Controls collection, I've noticed
with some 3rd party controls that if it is not added then it never gets
cleaned up. Another approach if this doesn't work is to GetAllControls on
form close and call dispose on the ones that implement IDispose.
.Net Memory Profiler is wonderful for finding these kinds of leaks. Not
sure if that is what you're using.
> Pete, thank you for your detailed response. From your words I guess I
> could
[quoted text clipped - 44 lines]
> Cheers
> David.
> Pete, thank you for your detailed response. From your words I guess I
> could
> not make myself perfectly clear.
I think you've been clear enough. But I'm not sure you fully understand
memory management in .NET.
> Of course I know, that calling the Dispose method of an object does not
> mean
[quoted text clipped - 11 lines]
> creating
> and holding.
Not that it necessarily matters here, but it's not the only reference that
exists. The Application class, for example, maintains a list of open
forms. As long as your form is open, it's referenced there as well.
> In OnMyFormDisposed I drop the reference
> m_myForm = null;
That's fine as far as it goes. But you should not overstate what that
does.
> I assumed, that now the form was disposed (what else should be the
> reason to
> fire the Disposed event)
The form is being disposed when your Disposed event handler is called, yes.
> and all of my selfmade references to the form were dropped.
You don't have control over all possible references. However, yes...I
would expect generally that the instance will eventually be collected,
assuming it's no longer reachable.
> I concluded that now the GC should have the possibility to clean up
> and free resources.
That's too vague. The disposal of the instance has already resulted in
cleaning up and freeing of resources. All that remains is for the memory
to be collected. And assuming the instance is no longer reachable from
_any_ data structure, yes...the collector has the possibility to collect
that memory.
> From this point I profiled the memory consumption of my application and
> saw,
[quoted text clipped - 4 lines]
> System.Windows.Forms.Control/ControlNativeWindow" is still pointing at my
> object.
What profiler are you using? Are you sure it's correct? If it is, note
that having a reference to your form is a necessary, but not sufficient,
condition to prevent collection.
> I assume that this has something to do with the controls which I
> place on the form: a ToolStrip with some ToolStripButtons and a
> TabControl
> containing a ListView.
Why do you make that assumption? Unless something else is referring to
those controls, those references would not prevent the collector from
collecting the instance. Do you have something else referring to those
controls? Have you ascertained for sure that those controls are in fact
what's referencing the form instance?
> With trial-and-error I meanwhile discovered that the form is only garbage
> collected if I call this.Dispose() inside a Form.Closing event handler
> AND I
> set the AllowItemReorder property of the contained ToolStrip to false.
> Why
> does this work? Frankly, I don't know.
I don't know either. Assuming this is a modeless form we're talking
about, I doubt that calling Dispose() has any effect at all (closing a
modeless form causes it to be disposed). As long as the ToolStrip in the
form isn't being referred to anywhere else in your code, it having a
reference to the form shouldn't have any effect.
I suppose there's a possibility that the AllowItemReorder property, when
set to true, causes your ToolStrip item to be referenced by something
else. But then presumably that would show up in your memory profiling.
Again: an instance being referenced does not in and of itself prevent the
instance from being collected. The object must be reachable. If you
think your form instance isn't being collected, you need to follow the
references all the way back to some "rooted" reference that causes the
instance to be reachable.
Pete
David Rosenkranz - 22 Apr 2008 19:37 GMT
Thanks again. I think this discussion really helps me a lot!
> I think you've been clear enough. But I'm not sure you fully understand
> memory management in .NET.
Well, you could say it this way :-)
> The form is being disposed when your Disposed event handler is called, yes.
What does it mean exactly when the Disposed event is fired? Is the form
being disposed or has it been disposed. In case of the latter (which I would
prefer because of the event's name) why is the value of the IsDisposed
property of my form still false when I read it inside the event handler?
> You don't have control over all possible references. However, yes...I
> would expect generally that the instance will eventually be collected,
> assuming it's no longer reachable.
> What profiler are you using? Are you sure it's correct? If it is, note
> that having a reference to your form is a necessary, but not sufficient,
> condition to prevent collection.
I am using both YourKit for .NET and Memprofiler. Memprofiler actually gave
me a new clue. It says, my form is being referenced by an event handler
connected to the ToolStrip instance I mentioned. And according to my profiler
this reference is the only remaining rooted reference to the disposed form.
> I don't know either. Assuming this is a modeless form we're talking
Yes, it is.
> about, I doubt that calling Dispose() has any effect at all (closing a
> modeless form causes it to be disposed). As long as the ToolStrip in the
> form isn't being referred to anywhere else in your code, it having a
> reference to the form shouldn't have any effect.
You are most certainly right about the effect of the DIspose() method. But
in my profilers I can see the effect, nevertheless.
The ToolStrip isn't referenced anywhere else in my code. It is being created
and used only in my form class.
> I suppose there's a possibility that the AllowItemReorder property, when
> set to true, causes your ToolStrip item to be referenced by something
[quoted text clipped - 5 lines]
> references all the way back to some "rooted" reference that causes the
> instance to be reachable.
Maybe it could help if I post or link to a screenshot of my profiler showing
the rooted reference to my form?
Thank you!
Peter Duniho - 22 Apr 2008 20:08 GMT
> [...]
>> The form is being disposed when your Disposed event handler is called,
[quoted text clipped - 5 lines]
> prefer because of the event's name) why is the value of the IsDisposed
> property of my form still false when I read it inside the event handler?
I don't know the precise answer to the question. However, assuming that
IsDisposed is false in your handler, I take that as a clear indication
that your handler is called during disposal, not after. What does the
Disposing property return? I suspect "true".
Still, I don't see how it matters. You haven't described any code in
which it matters whether disposal has been completed when your handler is
called.
> [...]
> I am using both YourKit for .NET and Memprofiler. Memprofiler actually
[quoted text clipped - 4 lines]
> this reference is the only remaining rooted reference to the disposed
> form.
A reference inside a ToolStrip instance isn't a rooted reference. Unless
something else refers to the ToolStrip instance, that reference doesn't
make your form instance reachable, and so that reference wouldn't prevent
the form from being collected.
Think of it this way: is your ToolStrip instance collectable? If not,
then neither is the form instance. If it is, then so is the form instance.
You haven't answered the question until you know whether the ToolStrip
instance is itself collectable.
> [...]
> You are most certainly right about the effect of the DIspose() method.
[quoted text clipped - 3 lines]
> created
> and used only in my form class.
If that's true, then it's not reachable and thus isn't preventing your
form from being collected.
So either your assertion about collection is wrong, or your assertion
about the references to the ToolStrip instance is wrong.
Pete
David Rosenkranz - 23 Apr 2008 15:12 GMT
> I don't know the precise answer to the question. However, assuming that
> IsDisposed is false in your handler, I take that as a clear indication
[quoted text clipped - 4 lines]
> which it matters whether disposal has been completed when your handler is
> called.
I read the IsDisposed property just for curiosity. Just to find out wether
the event handler is called _after_ or _during_ the disposal process.
To demonstrate what I mean, I just created a simple VS2008 project which you
can find on my website at http://www.david-rosenkranz.de/ToolStripper.zip
You may compile it yourself or just start the ToolStripper.exe from the
bin/debug directory and connect it to your favorite profiler.
The "application"'s main form just has a button. When you click it, second
form (instance of ToolStripForm) shows up, containing nothing more than a
ToolStrip with a handful of buttons. If you do nothing with this second form
and just close it, you will see in the profiler, that the ToolStripForm
instance will be garbage collected a few seconds after closing it. Fine so
far.
But, now click the button again to show a new instance of ToolStripForm.
Now, before closing it, just click on the ToolStripOverflowButton at the
bottom right of the ToolStrip to show the hidden buttons, which do not have
enough space in the bar. Click the ToolStripOverflowButton again, to hide the
additional buttons and then close the ToolStripForm. When I do this on my
machine, my profiler shows, that this ToolStripForm is not garbage collected
in a reasonable timespan. Take a memory snapshot and you will see that there
still IS a root path to the form. The path from root to the form also
contains the ToolStrip instance of this form.
In case you don't see the effect or don't want to start/profile my project
I've also put two screenshots onto my website, one from each profiler:
http://www.david-rosenkranz.de/yourkit.png
and
http://www.david-rosenkranz.de/memprofiler.png
In one of my earlier posts I said, that I experimented with the
AllowItemReorder property of the ToolStrip. In my small demo project, I have
this property set to false (default). If you set it "true" in the Form
Designer of VS and then start the application the ToolStripForm instances
will never be garbage collected, no matter if you pressed the overflow button
or not.
I think this behavior is quite strange, and I truly wonder, why I could not
find any report on this on the web...
Thank you!
Peter Duniho - 23 Apr 2008 17:50 GMT
> [...] When I do this on my
> machine, my profiler shows, that this ToolStripForm is not garbage
> collected
> in a reasonable timespan.
Not that this necessarily explains your issue, but there is no such thing
as "a reasonable timespan". The garbage collection will happen when it
happens. It could be seconds, minutes, or even hours, depending on what
else is happening.
> Take a memory snapshot and you will see that there
> still IS a root path to the form. The path from root to the form also
[quoted text clipped - 5 lines]
> and
> http://www.david-rosenkranz.de/memprofiler.png
Not being familiar with either tool, I can't say that the screenshots help
me that much. Maybe someone else will have better insight. The first
screenshot seems simpler to understand, and I gather that each line in the
lower pane represents a path that the profiler _believes_ to be a rooted
path to your instance. But I don't know enough about the profiler to know
whether those are reliable indicators. Nor did you expand any of the
multi-node paths to show what the actual root was.
> [...]
> I think this behavior is quite strange, and I truly wonder, why I could
> not
> find any report on this on the web...
Well, I don't have an answer for that question. There are two
possibilities: either this is a genuine issue that no one has ever noticed
before; or your tools are misleading you.
Absent any other information, I'd give each of those possibilities a
straight 50/50 chance of being correct. YMMV. :)
Pete
David Rosenkranz - 24 Apr 2008 13:05 GMT
Hi Pete (and everyone else)
> Not that this necessarily explains your issue, but there is no such thing
> as "a reasonable timespan". The garbage collection will happen when it
> happens. It could be seconds, minutes, or even hours, depending on what
> else is happening.
Of course, by definition, there is no "reasonable timespan" nor is there a
predictable result of a GC run. My assumptions are all based on experience
after watching in my profiler how the GC usually works when executing my app.
I think it would be most unlikely, that in the good cases the GC operates
quite fast and in all the bad cases (overflow button clicked or
AllowItemReorder==true) it doesn't work for hours. Possible - for sure - but
unlikely.
> whether those are reliable indicators. Nor did you expand any of the
> multi-node paths to show what the actual root was.
At http://david-rosenkranz.de/toolstripper.html you find a completely
expanded version of the rooted references tree to my form object. Maybe this
helps a little?
Cheers!
David.
Peter Duniho - 24 Apr 2008 18:15 GMT
> [...]
> At http://david-rosenkranz.de/toolstripper.html you find a completely
> expanded version of the rooted references tree to my form object. Maybe
> this
> helps a little?
Not for me, sorry. If I take that output at face value, all it's telling
me is that the ToolStrip instances are being considered "rooted" for the
same reason that your form is (i.e. somewhere there's an instance of the
"Control/ControlNativeWindow" type that has a member "control" that leads
to the actual Form and ToolStrip instances).
Presumably, if the form could be unrooted, so too would the ToolStrip
instances. All of this, of course, assumes that the memory profiler is
providing accurate information and I'm interpreting it correctly. Neither
of these are actually a given. Since it's all I have to go on, I'm afraid
I don't have anything more useful to add.
You really will need to find out why the "control" member is being
considered a root by your memory profiler. I'd say one place to start is,
"why does the type Control/ControlNativeWindow have a slash in it?" and
"who is actually storing the instance of that type?"
Pete
David Rosenkranz - 25 Apr 2008 11:15 GMT
..I just found an article which helped me to solve the problem. It seems,
that there is a problem with the ToolStrip-Elements in the .NET framework. If
a toolstrip has certain properties (eg. AllowItemReorder==true) or the user
does certain actions (eg clicking on the overflow button, or hiding/showing
the toolstrip) the ToolStrip implementation creates and registers event
handlers in a static event handler storage. Because static objects are GC
roots, the toolstrip and the surrounding form is now rooted and therfore
cannot be collected by GC. So you have to break that root path somehow. Best
would be to deregister the event handlers.
The article I found describes how to do this using ToolStripTextBox as an
example
http://www.scitech.se/blog/index.php/2007/10/05/memory-leak-in-toolstriptextboxc
ontrol/
Following the steps described in the article I discovered, that my problem
is caused by a UserPreferenceChangedEventHandler which is being registered by
the ToolStrip-Implementation when it shows a dropdown as the result of a
click on the ToolStripOverflowButton. The dropdown actually is a Toolstrip
itself, and is therefore a Control with a ControlNativeWindow. When the
control is created, a handle is created to. And on creation of the handle,
the event handler is being registered. So the solution to my problem would be
to destroy the handle, because that would deregister the eventhandler. I
don't know why this is not be done automatically when the dropdown is closed
or at least when the surrounding form is being disposed. So I do it myself:
in the the Dispose method of my form implementation (found in
MyForm.Designer.cs) I simply call
this.myBadToolstrip.OverflowButton.DropDown.Dispose();
This disposes the toolstrip dropdown, which implicitly destroys the
mentioned handle, which again deregisters the problematic event handler.
The problem which I could not solve up to now is that AllowItemReorder=true
also seems to register event handlers in a static registration. And I still
cannot see how to get rid of this. Simply setting
this.myBadToolstrip.AllowItemReorder=false in the Dispose method of my form
does not to the job.
I hope this might help somebody...
Cheers.