leaks in GC?

M

Michael Sander

Hi,
I'm pretty sure I must do something terrible wrong, but it surely does look
like some sort of leak in the GC. (.NET 1.1)
What I did:

created a form, dropped a ContextMenu on it, connected it to the form.
Then I added an emtpy MenuItem to the Menu. No events for the MenuItem,
nothing else.

If I create the Form

f = new Form2();
f.Show();

and free it afterwards

f.Close()
f.Dispose()
f = null;

everything is fine.

BUT:
If I do a right-click on the created form, so that the ContextMenu pops up,
the Form, ContextMenu and the MenuItem wont be collected by the GC.

I went through this by using a Memory-Profiler http://memprofiler.com/.

Can somebody explain to me, whats happening?

Regards,
Michael Sander


// SOURCE-CODE FOR THE EXAMPLE


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace LeakTest
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (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.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(40, 44);
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "create form";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(40, 88);
this.button2.Name = "button2";
this.button2.TabIndex = 1;
this.button2.Text = "destroy form";
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(164, 169);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);

}
#endregion

/// <summary>
/// Summary description for Form2.
/// </summary>
public class Form2 : System.Windows.Forms.Form
{
private System.Windows.Forms.ContextMenu contextMenu1;
private System.Windows.Forms.MenuItem menuItem1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public Form2()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(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.contextMenu1 = new System.Windows.Forms.ContextMenu();
this.menuItem1 = new System.Windows.Forms.MenuItem();
//
// contextMenu1
//
this.contextMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[]
{
this.menuItem1});
//
// menuItem1
//
this.menuItem1.Index = 0;
this.menuItem1.Text = "nothing";
//
// Form2
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.ContextMenu = this.contextMenu1;
this.Name = "Form2";
this.Text = "Form2";

}
#endregion
}

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

Form2 f;

private void button1_Click(object sender, System.EventArgs e)
{
f = new Form2();
f.Show();
}

private void button2_Click(object sender, System.EventArgs e)
{
f.Close();
f.Dispose();
f = null;
}
}
}
 
C

Chris Dunaway

Michael said:
BUT:
If I do a right-click on the created form, so that the ContextMenu pops up,
the Form, ContextMenu and the MenuItem wont be collected by the GC.

How long did you wait for the GC to kick in? The GC doesn't
necessarily reclaim the memory as soon as you call .Dispose. Are you
sure you waited long enough for the GC to kick in?
 
M

Michael Sander

yes, i guess so. Called it a couple of times and been waiting for something
like 5 Minutes.
 
L

Lucian Wischik

Michael Sander said:
f.Close()
f.Dispose()
f = null;
If I do a right-click on the created form, so that the ContextMenu pops up,
the Form, ContextMenu and the MenuItem wont be collected by the GC.

I don't know much about the .net garbage collector. But my
understanding is that it "Just Doesn't Work This Way." Things that
implement IDisposable do so because they have unmanaged resources that
must be got rid of at a certain time. A form has unmanaged resources
(the win32 HWNDs &c.) and by calling f.Dispose() you are explicitly
getting rid of them.

But the .net object, the WinForm formerly known as "f", remains.

You set "f" to null. Probably there are no more pointers to the form
in your program (unless the Application object retained a pointer to
it, though I doubt this). Therefore the form object is a CANDIDATE for
garbage collection.

But there's no reason why garbage collection should take place, even
within your five-minute wait. As MSDN says: "it is not possible to
predict when garbage collection will occur." I bet that garbage
collection is waiting until memory is tight before it bothers doing
any collection.

Specifically, MSDN says "The garbage collector's optimizing engine
determines the best time to perform a collection, based upon the
allocations being made." That suggests to me that garbagge-collection
is normally triggered by some subsequent "new" statements, not by
waiting 5 minutes.

Read the MSDN topic "GC class". Maybe try its sample code:

// Determine which generation myGCCol object is stored in.
Console.WriteLine("Generation: {0}", GC.GetGeneration(myGCCol));

// Determine the best available approximation of the number
// of bytes currently allocated in managed memory.
Console.WriteLine("Total Memory: {0}", GC.GetTotalMemory(false));

// Perform a collection of generation 0 only.
GC.Collect(0);

// Determine which generation myGCCol object is stored in.
Console.WriteLine("Generation: {0}", GC.GetGeneration(myGCCol));

Console.WriteLine("Total Memory: {0}", GC.GetTotalMemory(false));

// Perform a collection of all generations up to and including 2.
GC.Collect(2);
 
M

Michael Sander

to be exact: called GC.Collect a couple of times

Michael Sander said:
yes, i guess so. Called it a couple of times and been waiting for
something like 5 Minutes.
 
W

Willy Denoyette [MVP]

It doesn't mater how long that you wait, if you don't create any more
objects, the GC won't kick in.
The GC kicks in when an allocation threshold has been reached, that is if
you don't allocate any more objects (or only very few and small) after you
called Dispose, GC won't happen.

Willy.

| yes, i guess so. Called it a couple of times and been waiting for
something
| like 5 Minutes.
|
| | > Michael Sander wrote:
| >> BUT:
| >> If I do a right-click on the created form, so that the ContextMenu pops
| >> up,
| >> the Form, ContextMenu and the MenuItem wont be collected by the GC.
| >
| > How long did you wait for the GC to kick in? The GC doesn't
| > necessarily reclaim the memory as soon as you call .Dispose. Are you
| > sure you waited long enough for the GC to kick in?
| >
|
|
 
M

Michael Sander

yes your right, I didn't mention this earlier.

I DID call GC.Collect().

think of it like this:
f.Close()
f.Dispose()
f = null;
GC.Collect();

As for the unmanaged ressources:
I guess the GC should take care of all the components provided by .NET.
Otherwise we all would end up clicking huge foms together and spending hours
and hours afterwards trying to find all ressources that we must take care of
manually.
This you wouldnt call RAD or? :)

Besides the MemoryProfiler I had another try on this with an ArrayList of
WeakReferences to the Form, ContextMenu and MenuItem. They are never
released, never mind how often you call GC.Collect().
 
M

Michael Sander

Yepp, forgot to tell you guys that I've been calling GC.Collect() manually.
Besides, the small example is the result of tracking down a memory leak in
an rather huge app. There I've got dozends of objects beeing referenced by
the Form, ContextMenu and the MenuItem.
All of them will be released, if i don't right-click the form. But if I do,
they will all stay.
I've been watching the GC for a while, tracking all objects with Lists of
WeakReferences and calling GC.Collect() like hell.
Took me ages to realize that the reason is the simple .NET ContextMenu, not
my derived Components or other objetcs.

Michael
 
M

Michael Sander

Since I missed some code...
Exchange the previous button2_Click with this version:

private void button2_Click(object sender, System.EventArgs e)
{
if (f != null)
{
f.Close();
f.Dispose();
f = null;
}
GC.Collect();
}



Michael Sander said:
Hi,
I'm pretty sure I must do something terrible wrong, but it surely does
look like some sort of leak in the GC. (.NET 1.1)
What I did:

created a form, dropped a ContextMenu on it, connected it to the form.
Then I added an emtpy MenuItem to the Menu. No events for the MenuItem,
nothing else.

If I create the Form

f = new Form2();
f.Show();

and free it afterwards

f.Close()
f.Dispose()
f = null;

everything is fine.

BUT:
If I do a right-click on the created form, so that the ContextMenu pops
up, the Form, ContextMenu and the MenuItem wont be collected by the GC.

I went through this by using a Memory-Profiler http://memprofiler.com/.

Can somebody explain to me, whats happening?

Regards,
Michael Sander


// SOURCE-CODE FOR THE EXAMPLE


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace LeakTest
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (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.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(40, 44);
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "create form";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(40, 88);
this.button2.Name = "button2";
this.button2.TabIndex = 1;
this.button2.Text = "destroy form";
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(164, 169);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);

}
#endregion

/// <summary>
/// Summary description for Form2.
/// </summary>
public class Form2 : System.Windows.Forms.Form
{
private System.Windows.Forms.ContextMenu contextMenu1;
private System.Windows.Forms.MenuItem menuItem1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public Form2()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(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.contextMenu1 = new System.Windows.Forms.ContextMenu();
this.menuItem1 = new System.Windows.Forms.MenuItem();
//
// contextMenu1
//
this.contextMenu1.MenuItems.AddRange(new
System.Windows.Forms.MenuItem[] {
this.menuItem1});
//
// menuItem1
//
this.menuItem1.Index = 0;
this.menuItem1.Text = "nothing";
//
// Form2
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.ContextMenu = this.contextMenu1;
this.Name = "Form2";
this.Text = "Form2";

}
#endregion
}

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

Form2 f;

private void button1_Click(object sender, System.EventArgs e)
{
f = new Form2();
f.Show();
}

private void button2_Click(object sender, System.EventArgs e)
{
f.Close();
f.Dispose();
f = null;
}
}
}
 
A

Andy

Michael,

It doesn't look like your context menu objects where ever added to the
components object for Form2, so nothing ever calls disposed on your
context menus.
 
M

Michael Sander

Hm, yes your right.

I was wondering about that too. But since all this code has been generated
by the designer, it should work.
 
K

Kevin Westhead

Have you tried calling GC.WaitForPendingFinalizers() immediately after
GC.Collect()? It could be that something isn't being disposed correctly when
the form is disposed, which means the cleanup will only occur during
finalise.
 
A

Andy

I'm using .Net 2.0 and MenuStrips, but the code generated does add the
those controls for me to the components.

Maybe the designer isn't working right? Or maybe the code got removed
accidently?

At any rate you're not really disposing everything properly; Make sure
that those controls are disposed of, since i believe they'd hold a
reference to the form they are on (which means that can't be GCed).

Andy
 
M

Michael Sander

Good point, checked this and found out that most Controls are added to
this.Controls, but not the ContextMenu and not the MenuItems.

Is this 'by design' or just some flaw in my Environent? I can reproduce this
on at least 2 Systems...

Michael
 
M

Michael Sander

I tried this, and it works, if i first create the components list and then
add the contextmenu to it manually.
But it isnt meant to work like this, is it?

The designer isnt able to produce code that cleans up the ressources it
creates?
Im quite new to Visual Studio and C#, until now I found it rather
convincing, but this leaves some silly feelings in my stomach...
 
A

Andy

I can't answer that. Try adding them to the components collection
manually, and see if that solves your problem. If it does, perhaps it
is a bug.
 
L

Larry Lard

Michael said:
I tried this, and it works, if i first create the components list and then
add the contextmenu to it manually.
But it isnt meant to work like this, is it?

The designer isnt able to produce code that cleans up the ressources it
creates?
Im quite new to Visual Studio and C#, until now I found it rather
convincing, but this leaves some silly feelings in my stomach...


I would say this is a bug in VS2003. It's 'fixed' in VS2005: in
Framework 2.0, ContextMenu is replaced by ContextMenuStrip. One of the
ctors for ContextMenuStrip accepts an IContainer which is "A component
that implements IContainer that is the container of the
ContextMenuStrip." In the Remarks section of that ctor's documentation
we see quite clearly:

Remarks
Use this constructor to ensure that the ContextMenuStrip is disposed of
properly, since it is not a child of the form.

The code autogenerated by VS2005 uses this ctor. There is no such ctor
in Framework 1.1. Probably you would be able to find somewhere on msdn2
a list of known issues in 1.1 fixed in 2.0; this would be one such.

By way of reference, this is the code VS2003 creates when you drop a
ContextMenu on a form:

private void InitializeComponent()
{
this.contextMenu1 = new System.Windows.Forms.ContextMenu();
//no further reference to contextMenu1; in particular,
//no addition of it to this.components

And this is what VS2005 generates:

private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.contextMenuStrip1 = new
System.Windows.Forms.ContextMenuStrip(this.components);


So, to give the old support cliche, your problem would be fixed by
upgrading to the latest version...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top