First Time use - Garbage Collection ?

Use this forum for questions on how to use .NET Memory Profiler and how to analyse memory usage.
Post Reply
stijn
Posts: 23
Joined: Thu Jun 28, 2007 2:57 pm

First Time use - Garbage Collection ?

Post by stijn » Thu Jun 28, 2007 3:12 pm

Hi,
I am facing a big memory leak in my application, and the profiling dumps dont make much sense to me ...
to make it simpeler is made this basic program :

namespace WindowsApplication8
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.button1 = new System.Windows.Forms.Button();
this.menuStrip1.SuspendLayout();
this.SuspendLayout();
//
// menuStrip1
//
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.fileToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(593, 24);
this.menuStrip1.TabIndex = 0;
this.menuStrip1.Text = "menuStrip1";
//
// fileToolStripMenuItem
//
this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.helpToolStripMenuItem});
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
this.fileToolStripMenuItem.Size = new System.Drawing.Size(35, 20);
this.fileToolStripMenuItem.Text = "File";
//
// helpToolStripMenuItem
//
this.helpToolStripMenuItem.Name = "helpToolStripMenuItem";
this.helpToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
this.helpToolStripMenuItem.Text = "Help";
this.helpToolStripMenuItem.Click += new System.EventHandler(this.helpToolStripMenuItem_Click);
//
// button1
//
this.button1.Location = new System.Drawing.Point(502, 46);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 2;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(593, 401);
this.Controls.Add(this.button1);
this.Controls.Add(this.menuStrip1);
this.IsMdiContainer = true;
this.MainMenuStrip = this.menuStrip1;
this.Name = "Form1";
this.Text = "Form1";
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();

}

#endregion

private System.Windows.Forms.MenuStrip menuStrip1;
private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem;
private System.Windows.Forms.Button button1;

private void helpToolStripMenuItem_Click(object sender, EventArgs e)
{
Form _view;
_view = new frmHelp();
_view.ShowDialog();
_view.Close();
_view.Dispose();
_view = null;
}

private void button1_Click(object sender, EventArgs e)
{
Form _view;
_view = new frmHelp();
_view.ShowDialog();
_view.Close();
_view.Dispose();
}
}
}



After every click on the menu item or the button,
a new form was loaded in the heap, but the old ones didnt dissapear according to ur profiler ...
I thought I read somewhere that the profiler would perform a garbage collection before every snapshot ....

Just before posting this to find out what the problem is , I tried performing the GC myself after the close and
My forms dissappeared from the heap ... so what gives ???
Does the profiler perform a GC itself ???

And is it normal, that is I open and close that windows 10 times and more, that it takes for ever for them to dissapear from the heap ... when would an automatic GC occur ?

Andreas Suurkuusk
Posts: 1029
Joined: Wed Mar 02, 2005 7:53 pm

Post by Andreas Suurkuusk » Thu Jun 28, 2007 7:30 pm

I don't know what the frmHelp class looks like, so I cannot tell whether there is something that will prevent it from being GCed.

However, when collecting a heap snapshot, several full GCs are performed, so if the form is collected by a call to System.GC.Collect, then it should normally be collected when a heap snapshot is collected (if I remember correctly, the message queue is flushed when System.GC.Collect is called, which it's not when a heap snapshot is collected, so the behaviour might be a little different).

Are you collecting a heap snapshot after each time you clicked the button? Have you investigated the details of the unexpected new frmHelp instances? What root paths are keeping the instances alive?
Best regards,

Andreas Suurkuusk
SciTech Software AB

stijn
Posts: 23
Joined: Thu Jun 28, 2007 2:57 pm

Post by stijn » Thu Jun 28, 2007 9:19 pm

Well, the frmHelp Class isnt very much , just a form nothing more,
I collected after every open and close ...

:
public partial class frmHelp : Form
{
public frmHelp()
{
InitializeComponent();
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.SuspendLayout();
//
// frmHelp
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(553, 425);
this.Name = "frmHelp";
this.Text = "frmHelp";
this.ResumeLayout(false);

}

#endregion
}

Delta after first open and close :

Namespace Name Live instances Delta live instances Live bytes Max live instance size Min live instance size Delta live bytes Unreachable instances
Unchecked WindowsApplication8 frmHelp 1 1 324 324 324 324 0


does reappear second time, must have dreamed it , or fixed it ....

original problem still appears tho :
with this rootpath
System EventHandler
System.ComponentModel EventHandlerList.ListEntry
System.ComponentModel EventHandlerList
System.Windows.Forms ToolStripButton
System Object[]
System.Collections ArrayList
System.Windows.Forms ToolStripItemCollection
System.Windows.Forms ToolStrip
System EventHandler
System Object[]
System EventHandler
System Delegate[]
System.Windows.Forms ToolStripManager staticEventHandlers

stijn
Posts: 23
Joined: Thu Jun 28, 2007 2:57 pm

Post by stijn » Thu Jun 28, 2007 9:21 pm

Read some stuf with menu items being bugged in 1.1,
tho i am using c# 2.0

applied following code in dispose :
private void ClearBindings(Control c)
{
Binding[] bindings = new Binding[c.DataBindings.Count];
c.DataBindings.CopyTo(bindings, 0);
c.DataBindings.Clear();
foreach (Binding binding in bindings)
{
TypeDescriptor.Refresh(binding.DataSource);
}
foreach (Control cc in c.Controls)
{
ClearBindings(cc);
}
}


because I read it might help fix the bug in 1.1

Andreas Suurkuusk
Posts: 1029
Joined: Wed Mar 02, 2005 7:53 pm

Post by Andreas Suurkuusk » Mon Jul 02, 2007 6:26 pm

Looking at the data you provided, I realized that you are attaching to the process. When attaching, no GC is performed when a heap snapshot is collected, since the profiler is just investigating the memory of the profiled process at the time of the snapshot. It has no way of affecting the process (e.g. forcing a GC).

Attaching to a process will provide less information than when you start the process from the profiler and is better suited for production code profiling. If possible, I recommend that you start the process from the profiler (e.g. using the File->Profile Application command).

However, even if a garbage collect is not performed, the frmHelp instance should probably be "unreachable" after calling Dispose (and leaving the event handler method). The root path you provided, is that for a frmHelp instance? Is seems like the instance is rooted via a ToolStripButton, which is a bit strange for an empty Form.
Best regards,

Andreas Suurkuusk
SciTech Software AB

stijn
Posts: 23
Joined: Thu Jun 28, 2007 2:57 pm

Post by stijn » Tue Jul 03, 2007 1:05 pm

Hi,

I currently always test for memory leaks by using the attach to process, because this way i can easily trace and alter code at debug time in the visual studio debugger .... (ie. check if the leak is worse or better if I skip some pieces of code) , without having to rebuild the application everytime ...

The rootpath provided, was the one for my original form that caused the problem, the one that caused me to try and reproduce it with the frmhelp example ... (wich probably worked as it was supposed to do, if no GC was happening)

So the situation is : a form is attached to a mdi form, its toolstrip reparented ...
in the close and dispose of the form, I expicitly remove the toolstrip from the mdi parent, set it to null and explicitly clear and dispose all bindingsources, datagrid and datatables
but keep getting this rootpath :
System EventHandler
System.ComponentModel EventHandlerList.ListEntry
System.ComponentModel EventHandlerList
System.Windows.Forms ToolStripButton
System Object[]
System.Collections ArrayList
System.Windows.Forms ToolStripItemCollection
System.Windows.Forms ToolStrip
System EventHandler
System Object[]
System EventHandler
System Delegate[]
System.Windows.Forms ToolStripManager staticEventHandlers


any hints as how to find out, what causes this leak ?

Andreas Suurkuusk
Posts: 1029
Joined: Wed Mar 02, 2005 7:53 pm

Post by Andreas Suurkuusk » Wed Jul 04, 2007 7:52 pm

The root path shows that your form is kept alive because it is referenced by an event handler on a ToolStripButton, which in turn is part of a ToolStrip that is referenced by a static event handler.

You mention that you remove the ToolStrip and set it to null, so I assume that you do not intend the ToolStrip to be kept in memory. In this case you must call Dispose on the ToolStrip. Normally this would be done automatically when you dispose the parent Form, but since you have removed it from the Form this will not happen.

Are you disposing the removed ToolStrip?

If the intention is to keep the ToolStrip alive, then you will need to either remove the ToolStripButton from the ToolStrip, or the event handler from the ToolStripButton.
Best regards,

Andreas Suurkuusk
SciTech Software AB

stijn
Posts: 23
Joined: Thu Jun 28, 2007 2:57 pm

Post by stijn » Thu Jul 05, 2007 8:42 pm

Hi,
I already removed the reference to the toolstrip in the dispose of my form,
now I added the dispose, but that did not solve the problem,

In the mean time my license arrived , and I profiled it again, from within VS this time,
so now i also have allocation stacks and maybe other new functions so I might get more confused

I still get the same rootpath, but am not sure, how to make out which toolstrip it is and how to dereference it ....

this is what I do in the main form, in the close function :

if (this.toolStripPanel1.Controls.Contains(_currview.ToolStrip))
this.toolStripPanel1.Controls.Remove(_currview.ToolStrip);
_currview.BroadCastMessage -= new MessageToView(frmMain_BroadCastMessage);
(_currview as Form).Activated -= new EventHandler(View_Activated);
(_currview as Form).MdiParent = null;
_views.Remove(_currViewName);
(_currview as Form).Close();
(_currview as Form).Dispose();
_currview = null;

and this what happens in the dispose of the subview :
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
DIASBase.EventObjects.EventBroker.Instance.RefreshEvent -= new DIASBase.EventObjects.RefreshEventHandler(Instance_RefreshEvent);

if (_doctypeIsFollowUp != null)
_doctypeIsFollowUp.Clear();
_doctypeIsFollowUp = null;

if (tbl != null)
this.tbl.Dispose();
if (this.toolStrip1 != null)
{
this.toolStrip1.Dispose();
this.toolStrip1 = null;
}
if (this.ctrlFlowOverview1 != null)
{
this.ctrlFlowOverview1.Dispose();
this.ctrlFlowOverview1 = null;
}
if (_prefs != null)
{
_prefs = null;
}


this.archGrid.SelectionChanged -= new EventHandler(archGrid_SelectionChanged);
this.archGrid.DataSource = null;
if (documentTypeobjDataGridViewComboBoxColumn.DataSource != null)
{
(documentTypeobjDataGridViewComboBoxColumn.DataSource as BindingSource).Clear();
documentTypeobjDataGridViewComboBoxColumn.DataSource = null;
}
documentTypeobjDataGridViewComboBoxColumn.DataSource = null;

if (this.ttArchiveDataTableBindingSource != null)
{
(this.ttArchiveDataTableBindingSource.DataSource as TTArchiveDataTable).Clear();
(this.ttArchiveDataTableBindingSource.DataSource as TTArchiveDataTable).Dispose();
this.ttArchiveDataTableBindingSource.Dispose();
this.ttArchiveDataTableBindingSource.DataSource = null;
this.ttArchiveDataTableBindingSource = null;
}
if (_ds != null)
{
_ds.Clear();
_ds.Dispose();
_ds = null;
}

ClearBindings(this);
components.Dispose();
}
base.Dispose(disposing);
}


private void ClearBindings(Control c)
{
Binding[] bindings = new Binding[c.DataBindings.Count];
c.DataBindings.CopyTo(bindings, 0);
c.DataBindings.Clear();
foreach (Binding binding in bindings)
{
TypeDescriptor.Refresh(binding.DataSource);
}
foreach (Control cc in c.Controls)
{
ClearBindings(cc);
}
}

Andreas Suurkuusk
Posts: 1029
Joined: Wed Mar 02, 2005 7:53 pm

Post by Andreas Suurkuusk » Fri Jul 06, 2007 2:56 pm

There is a memory bug related to ToolStripTextBox, but as far as I know, it does not exist in ToolStrip or ToolStripButton. So when ToolStrip.Dispose() is called, I think it should correctly remove all static event handlers. Are you sure that your Dispose method is executed (e.g. is the "components" field non-null)? The dispose tracker will show you whether the ToolStrip instance has been disposed (but not when attaching).

To provide you with additional help, I think it would be easiest if you sent us a session file. If possible, I recommend that you create the session file by starting the process from the profiler (i.e. not attaching).

If the compressed session file is not too big, you can send it to support@scitech.se. If it's bigger than a few megabytes, please contact us at support@scitech.se and I will provide you with information on how to upload the file to our ftp-server.
Best regards,

Andreas Suurkuusk
SciTech Software AB

stijn
Posts: 23
Joined: Thu Jun 28, 2007 2:57 pm

Post by stijn » Tue Jul 10, 2007 2:24 pm

Have sent the session dump,

took 2 collections,
first time after first time opening and closing the form ,
seconde time after reopening and closing the form

name of the form is DIASUI.FLOWUI.vwMyFlow.cs

Andreas Suurkuusk
Posts: 1029
Joined: Wed Mar 02, 2005 7:53 pm

Post by Andreas Suurkuusk » Wed Jul 18, 2007 5:48 am

Now I have investigated the session file you provided us with. I managed to reproduce your problem and also to find a workaround for it.

The ToolStrip uses the VisibleChanged event to add and remove event handlers from global events, like SystemEvents.UserPreferencesChanged. Unfortunately, the VisibleChanged event is not always invoked correctly.

In your case the problem is that when you dispose a removed ToolStrip, the VisibleChanged event is raised and the Visible property is true. This happens in the DestroyWindow call, while disposing the ToolStrip!

So the ToolStrip.Dispose method forcible removes the global event handlers before calling base.Dispose. The base Dispose method causes the VisibleChanged event to happen, and the event handlers are added again, causing the memory leak.

The workaround for this problem is rather simple. You just need to set the Visible property of the ToolStrip to false before you dispose it, e.g. after having removed it from the ToolStripPanel.

Code: Select all

toolStripPanel.Controls.Remove( toolStrip );
toolStrip.Visible = false;
This will inhibit the VisibleChanged event in the Dispose method, and the ToolStrip shold be correctly collected.

P.S.
I didn't know about the problem with VisibleChanged being raised when disposing, but I have reported several times about another problem with VisibleChanged to the microsoft.public.dotnet.framework.windowsforms newsgroup (before even .NET Framework 1.0 was released and with very little response). For more information about this, see the following newsgroup entries:

http://tinyurl.com/39any2
http://tinyurl.com/2novze
http://tinyurl.com/3doje7
Best regards,

Andreas Suurkuusk
SciTech Software AB

stijn
Posts: 23
Joined: Thu Jun 28, 2007 2:57 pm

Post by stijn » Wed Jul 18, 2007 1:10 pm

Thank you very much for your answer
and your time ...

Is there any way one could deduct the leak is a microsoft leak , by looking at the session dump ?

Andreas Suurkuusk
Posts: 1029
Joined: Wed Mar 02, 2005 7:53 pm

Post by Andreas Suurkuusk » Tue Jul 24, 2007 6:36 am

The session file you provided gave a good clue that the memory leak was created by Microsoft code.

I started by looking at one of your DIASUI.FLOWUI.vwMyFlowInstances. Since it was disposed, it is clearly an indication of some sort of memory leak. Then I looked at one of the root paths of the form:

System.EventHandler #63,002
System.ComponentModel.EventHandlerList.ListEntry #62,994
System.ComponentModel.EventHandlerList #62,574
System.Windows.Forms.ToolStripButton #61,814
System.Object[] #62,561
System.Collections.ArrayList #62,560
System.Windows.Forms.ToolStripItemCollection #62,517
System.Windows.Forms.ToolStrip #62,522
Microsoft.Win32.UserPreferenceChangedEventHandler #63,395
Microsoft.Win32.SystemEvents.SystemEventInvokeInfo #61,731
Microsoft.Win32.SystemEvents.SystemEventInvokeInfo[] #38,241
System.Collections.Generic.List<SystemEvents.SystemEventInvokeInfo> #36,866
System.Collections.Generic.Dictionary<object, List<SystemEvents.SystemEventInvokeInfo>>.Entry[] #36,848
System.Collections.Generic.Dictionary<object, List<SystemEvents.SystemEventInvokeInfo>> #36,850
Microsoft.Win32.SystemEvents _handlers

As previously mentioned, it contains a ToolStrip instance. Investigating the ToolStrip instance shows that it is also disposed, and is rooted via Microsoft.Win32.UserPreferenceChangedEventHandler (#63,395). This is an event handler for a global SystemEvents which needs to be explicitly removed. Double clicking on the event handler reveals the following allocation stack (shortened for clarity):

ToolStrip.HookStaticEvents(bool)
ToolStrip.OnVisibleChanged(EventArgs)
...
Control.DestroyHandle()
Control.Dispose(bool)
ToolStrip.Dispose(bool)
Component.Dispose()
vwMyFlow.Dispose(bool)
Component.Dispose()
Form.Close()
...

This call stack is very surprising. The event handler was created while disposing the ToolStrip, not when the ToolStrip was initialized. This, I think, is a clear indication of that the memory leak is created by Microsoft code. Further investigation shows that the VisibleChanged event is raised when disposing a Control that has been removed from its parent. And strangely enough the Visible property is set to true! It seems like the ToolStrip uses the Visible property to hook to static events in the VisibleChanged event handler. This can can be verified using Reflector. By using Reflector you can also see that HookStaticEvents( false ) is called in the Dispose method, thus removing the static event handlers. But when base.Dispose is called, they are added again in the OnVisibleChanged event handler.

As mentioned in my previous post, the solution is to set the Visible property to false before disposing the ToolStrip. This will inhibit the VisibleChanged event when disposing.
Best regards,

Andreas Suurkuusk
SciTech Software AB

Post Reply

Who is online

Users browsing this forum: No registered users and 14 guests