Memory leak when working with datasets

J

Jon Abaunza

I have an application that has a Form manager that manages the life
cycle of the Forms and that pushes, pops or removes the forms depending
on the defined form stack size.

After everything worked nice, I started analyzing the memory of the
program to realize that i was having a memory leak of ~100 KB/form.

As I paint myself some controls and make some other p/invokes I thought
that it could have been that i didn't release memory correctly. But
after revising many times the code I started to make some experiments
with my code.

If I did not make any data Access (DB: SQLite) the memory leak was
inexistent ( This demonstrated that the graphical objects released
correctly their resources when the forms where destroied).

Then I made another two experiments:
The first one testing the SQLite Engine. (A simple query was made and
put it into a Datagrid) It's time I loaded the datagrid the leak was
getting bigger. But if I commented the line Datagrid.DataSource =
myDataSet.Tables[0]; the leak was inexistent.
(This demonstrated that the DB motor did not have any problem and that
theDatagrid or the DataSet should be wrong).

How could i get to release this memory? What could be the reason why is
not being released?
I would really apreciate any ideas.

Thanks in advance!
Jon
 
I

Ilya Tumanov [MS]

Are you getting OutOfMemoryException? If not, you probably don't have a
memory leak.

Please see this:
http://www.danielmoth.com/Blog/2005/01/memory-problems-faq.html



Also, are you disposing of objects with native resources properly (Forms,
controls, connections, command, etc.)?

If you're not disposing of these objects, you should.


Best regards,

Ilya

This posting is provided "AS IS" with no warranties, and confers no rights.

*** Want to find answers instantly? Here's how... ***

1. Go to
http://groups-beta.google.com/group/microsoft.public.dotnet.framework.compactframework?hl=en
2. Type your question in the text box near "Search this group" button.
3. Hit "Search this group" button.
4. Read answer(s).
 
J

Jon Abaunza

I repeated another simple experiment getting a System out of memory
exception: The project contains the following files: Form1.cs,
FormManager.cs, Program.cs.
Curiously if the line in the Form1.cs " dataGrid1.DataSource =
data.Tables[0];" is commented the memory leak is inexistent. It really
seems to be a bug in the garbage collector of the compact framework.
(it just happens in the compact framework).

->>>>Form1.cs


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.SQLite;

namespace DeviceApplication3
{

public partial class Form1 : Form
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.MainMenu mainMenu1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.DataGrid dataGrid1;
private Timer timer1;
DataSet data;
public Form1(DataSet data)
{
this.data = data;
InitializeComponent();

}

void Form1_Closing(object sender, CancelEventArgs e)
{
data = null;
timer1.Dispose();
dataGrid1.Dispose();
label1.Dispose();
mainMenu1.Dispose();
}


private void Form1_Load(object sender, EventArgs e)
{
timer1.Enabled = true;
}
#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.mainMenu1 = new System.Windows.Forms.MainMenu();
this.label1 = new System.Windows.Forms.Label();
this.dataGrid1 = new System.Windows.Forms.DataGrid();
this.timer1 = new System.Windows.Forms.Timer();
this.SuspendLayout();
//
// label1
//
this.label1.Location = new System.Drawing.Point(4, 4);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(222, 20);
this.label1.Text = "Memory use:";
//
// dataGrid1
//
this.dataGrid1.BackgroundColor =
System.Drawing.Color.FromArgb(((int)(((byte)(128)))),
((int)(((byte)(128)))), ((int)(((byte)(128)))));
this.dataGrid1.Location = new System.Drawing.Point(0, 40);
this.dataGrid1.Name = "dataGrid1";
this.dataGrid1.Size = new System.Drawing.Size(240, 200);
this.dataGrid1.TabIndex = 2;
//
// timer1
//
this.timer1.Tick += new
System.EventHandler(this.timer1_Tick);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F,
96F);
this.AutoScaleMode =
System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoScroll = true;
this.ClientSize = new System.Drawing.Size(240, 268);
this.Controls.Add(this.dataGrid1);
this.Controls.Add(this.label1);
this.Menu = this.mainMenu1;
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);

}
/// <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);
}

#endregion

private void timer1_Tick(object sender, EventArgs e)
{
timer1.Enabled = false;
label1.Text = "Memory use: " + GC.GetTotalMemory(true);
label1.Refresh();
dataGrid1.DataSource = data.Tables[0];
}

}
}


->>>>>FormManager.cs



using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;


namespace DeviceApplication3
{
public class FormManager
{
List<Form> forms;
int maxFormNumber = 3;
Form applicationForm;
DataSet data;
public FormManager()
{
//I create the dummy data
data = new DataSet();
data.Tables.Add();
for (int j = 0; j < 10; j++)
{
data.Tables[0].Columns.Add();
}
for (int i = 0; i < 1000; i++)
{
DataRow dr = data.Tables[0].NewRow();
for (int j = 0; j < 10; j++)
{
dr[j] = "123123212312312312" +
Environment.TickCount.ToString() + "1241455357367465daff" +
Environment.TickCount;
}
data.Tables[0].Rows.Add(dr);
}
forms = new List<Form>();
//I Don't destroy the form that has the event pool (There
should just be always maxFormNumber+1 forms in memory)
applicationForm = new Form1(data);
forms.Add(applicationForm);
applicationForm.Show();

}

public void ExecuteAction()
{
Form form;
form = new Form1(data);
PushForm(form);
}
public void PushForm(Form form)
{
form.Show();
forms[forms.Count - 1].Hide();
forms.Add(form);
if (forms.Count > maxFormNumber)
{
if (forms[0] != applicationForm)
{
forms[0].Close();
forms[0].Dispose();
}
forms.RemoveAt(0);
}

}
public Form getCurrentForm()
{
return forms[forms.Count - 1];
}

}
}




->>>>>Program.cs


using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Data;

namespace DeviceApplication3
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>

static Timer timer;
static int counter;
static FormManager manager;
[MTAThread]
static void Main()
{

timer = new Timer();
timer.Interval = 100;
timer.Tick += new EventHandler(timer_Tick);
manager = new FormManager();
timer.Enabled = true;
Application.Run(manager.getCurrentForm());
}

static void timer_Tick(object sender, EventArgs e)
{
timer.Enabled = false;
counter++;
manager.ExecuteAction();
if (counter % 100 == 0)
{
MessageBox.Show("Memory use: " +
GC.GetTotalMemory(true));
}
timer.Enabled = true;
}
}
}
 
G

Ginny Caughey [MVP]

Jon,

I was able to reproduce your OutOfMemoryException with your sample. I just
commented out the MessageBox call so the app could run continuously and
after about a half hour it did crash.

--
Ginny Caughey
..NET Compact Framework MVP


Jon Abaunza said:
I repeated another simple experiment getting a System out of memory
exception: The project contains the following files: Form1.cs,
FormManager.cs, Program.cs.
Curiously if the line in the Form1.cs " dataGrid1.DataSource =
data.Tables[0];" is commented the memory leak is inexistent. It really
seems to be a bug in the garbage collector of the compact framework.
(it just happens in the compact framework).

->>>>Form1.cs


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.SQLite;

namespace DeviceApplication3
{

public partial class Form1 : Form
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.MainMenu mainMenu1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.DataGrid dataGrid1;
private Timer timer1;
DataSet data;
public Form1(DataSet data)
{
this.data = data;
InitializeComponent();

}

void Form1_Closing(object sender, CancelEventArgs e)
{
data = null;
timer1.Dispose();
dataGrid1.Dispose();
label1.Dispose();
mainMenu1.Dispose();
}


private void Form1_Load(object sender, EventArgs e)
{
timer1.Enabled = true;
}
#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.mainMenu1 = new System.Windows.Forms.MainMenu();
this.label1 = new System.Windows.Forms.Label();
this.dataGrid1 = new System.Windows.Forms.DataGrid();
this.timer1 = new System.Windows.Forms.Timer();
this.SuspendLayout();
//
// label1
//
this.label1.Location = new System.Drawing.Point(4, 4);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(222, 20);
this.label1.Text = "Memory use:";
//
// dataGrid1
//
this.dataGrid1.BackgroundColor =
System.Drawing.Color.FromArgb(((int)(((byte)(128)))),
((int)(((byte)(128)))), ((int)(((byte)(128)))));
this.dataGrid1.Location = new System.Drawing.Point(0, 40);
this.dataGrid1.Name = "dataGrid1";
this.dataGrid1.Size = new System.Drawing.Size(240, 200);
this.dataGrid1.TabIndex = 2;
//
// timer1
//
this.timer1.Tick += new
System.EventHandler(this.timer1_Tick);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F,
96F);
this.AutoScaleMode =
System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoScroll = true;
this.ClientSize = new System.Drawing.Size(240, 268);
this.Controls.Add(this.dataGrid1);
this.Controls.Add(this.label1);
this.Menu = this.mainMenu1;
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);

}
/// <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);
}

#endregion

private void timer1_Tick(object sender, EventArgs e)
{
timer1.Enabled = false;
label1.Text = "Memory use: " + GC.GetTotalMemory(true);
label1.Refresh();
dataGrid1.DataSource = data.Tables[0];
}

}
}


->>>>>FormManager.cs



using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;


namespace DeviceApplication3
{
public class FormManager
{
List<Form> forms;
int maxFormNumber = 3;
Form applicationForm;
DataSet data;
public FormManager()
{
//I create the dummy data
data = new DataSet();
data.Tables.Add();
for (int j = 0; j < 10; j++)
{
data.Tables[0].Columns.Add();
}
for (int i = 0; i < 1000; i++)
{
DataRow dr = data.Tables[0].NewRow();
for (int j = 0; j < 10; j++)
{
dr[j] = "123123212312312312" +
Environment.TickCount.ToString() + "1241455357367465daff" +
Environment.TickCount;
}
data.Tables[0].Rows.Add(dr);
}
forms = new List<Form>();
//I Don't destroy the form that has the event pool (There
should just be always maxFormNumber+1 forms in memory)
applicationForm = new Form1(data);
forms.Add(applicationForm);
applicationForm.Show();

}

public void ExecuteAction()
{
Form form;
form = new Form1(data);
PushForm(form);
}
public void PushForm(Form form)
{
form.Show();
forms[forms.Count - 1].Hide();
forms.Add(form);
if (forms.Count > maxFormNumber)
{
if (forms[0] != applicationForm)
{
forms[0].Close();
forms[0].Dispose();
}
forms.RemoveAt(0);
}

}
public Form getCurrentForm()
{
return forms[forms.Count - 1];
}

}
}




->>>>>Program.cs


using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Data;

namespace DeviceApplication3
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>

static Timer timer;
static int counter;
static FormManager manager;
[MTAThread]
static void Main()
{

timer = new Timer();
timer.Interval = 100;
timer.Tick += new EventHandler(timer_Tick);
manager = new FormManager();
timer.Enabled = true;
Application.Run(manager.getCurrentForm());
}

static void timer_Tick(object sender, EventArgs e)
{
timer.Enabled = false;
counter++;
manager.ExecuteAction();
if (counter % 100 == 0)
{
MessageBox.Show("Memory use: " +
GC.GetTotalMemory(true));
}
timer.Enabled = true;
}
}
}
 
I

Ilya Tumanov [MS]

Please try the following change:

protected override void Dispose(bool disposing)
{
dataGrid1.DataSource = null;

if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

Note Form1_Closing() is never called. Also, I do not see any difference
between desktop and NETCF.


Best regards,

Ilya

This posting is provided "AS IS" with no warranties, and confers no rights.

*** Want to find answers instantly? Here's how... ***

1. Go to
http://groups-beta.google.com/group/microsoft.public.dotnet.framework.compactframework?hl=en
2. Type your question in the text box near "Search this group" button.
3. Hit "Search this group" button.
4. Read answer(s).

Jon Abaunza said:
I repeated another simple experiment getting a System out of memory
exception: The project contains the following files: Form1.cs,
FormManager.cs, Program.cs.
Curiously if the line in the Form1.cs " dataGrid1.DataSource =
data.Tables[0];" is commented the memory leak is inexistent. It really
seems to be a bug in the garbage collector of the compact framework.
(it just happens in the compact framework).

->>>>Form1.cs


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.SQLite;

namespace DeviceApplication3
{

public partial class Form1 : Form
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.MainMenu mainMenu1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.DataGrid dataGrid1;
private Timer timer1;
DataSet data;
public Form1(DataSet data)
{
this.data = data;
InitializeComponent();

}

void Form1_Closing(object sender, CancelEventArgs e)
{
data = null;
timer1.Dispose();
dataGrid1.Dispose();
label1.Dispose();
mainMenu1.Dispose();
}


private void Form1_Load(object sender, EventArgs e)
{
timer1.Enabled = true;
}
#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.mainMenu1 = new System.Windows.Forms.MainMenu();
this.label1 = new System.Windows.Forms.Label();
this.dataGrid1 = new System.Windows.Forms.DataGrid();
this.timer1 = new System.Windows.Forms.Timer();
this.SuspendLayout();
//
// label1
//
this.label1.Location = new System.Drawing.Point(4, 4);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(222, 20);
this.label1.Text = "Memory use:";
//
// dataGrid1
//
this.dataGrid1.BackgroundColor =
System.Drawing.Color.FromArgb(((int)(((byte)(128)))),
((int)(((byte)(128)))), ((int)(((byte)(128)))));
this.dataGrid1.Location = new System.Drawing.Point(0, 40);
this.dataGrid1.Name = "dataGrid1";
this.dataGrid1.Size = new System.Drawing.Size(240, 200);
this.dataGrid1.TabIndex = 2;
//
// timer1
//
this.timer1.Tick += new
System.EventHandler(this.timer1_Tick);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F,
96F);
this.AutoScaleMode =
System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoScroll = true;
this.ClientSize = new System.Drawing.Size(240, 268);
this.Controls.Add(this.dataGrid1);
this.Controls.Add(this.label1);
this.Menu = this.mainMenu1;
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);

}
/// <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);
}

#endregion

private void timer1_Tick(object sender, EventArgs e)
{
timer1.Enabled = false;
label1.Text = "Memory use: " + GC.GetTotalMemory(true);
label1.Refresh();
dataGrid1.DataSource = data.Tables[0];
}

}
}


->>>>>FormManager.cs



using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;


namespace DeviceApplication3
{
public class FormManager
{
List<Form> forms;
int maxFormNumber = 3;
Form applicationForm;
DataSet data;
public FormManager()
{
//I create the dummy data
data = new DataSet();
data.Tables.Add();
for (int j = 0; j < 10; j++)
{
data.Tables[0].Columns.Add();
}
for (int i = 0; i < 1000; i++)
{
DataRow dr = data.Tables[0].NewRow();
for (int j = 0; j < 10; j++)
{
dr[j] = "123123212312312312" +
Environment.TickCount.ToString() + "1241455357367465daff" +
Environment.TickCount;
}
data.Tables[0].Rows.Add(dr);
}
forms = new List<Form>();
//I Don't destroy the form that has the event pool (There
should just be always maxFormNumber+1 forms in memory)
applicationForm = new Form1(data);
forms.Add(applicationForm);
applicationForm.Show();

}

public void ExecuteAction()
{
Form form;
form = new Form1(data);
PushForm(form);
}
public void PushForm(Form form)
{
form.Show();
forms[forms.Count - 1].Hide();
forms.Add(form);
if (forms.Count > maxFormNumber)
{
if (forms[0] != applicationForm)
{
forms[0].Close();
forms[0].Dispose();
}
forms.RemoveAt(0);
}

}
public Form getCurrentForm()
{
return forms[forms.Count - 1];
}

}
}




->>>>>Program.cs


using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Data;

namespace DeviceApplication3
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>

static Timer timer;
static int counter;
static FormManager manager;
[MTAThread]
static void Main()
{

timer = new Timer();
timer.Interval = 100;
timer.Tick += new EventHandler(timer_Tick);
manager = new FormManager();
timer.Enabled = true;
Application.Run(manager.getCurrentForm());
}

static void timer_Tick(object sender, EventArgs e)
{
timer.Enabled = false;
counter++;
manager.ExecuteAction();
if (counter % 100 == 0)
{
MessageBox.Show("Memory use: " +
GC.GetTotalMemory(true));
}
timer.Enabled = true;
}
}
}
 
J

Jon Abaunza

Thank you for the answer!
With the code with the changes the memory leak is smaller than before
but it still persists (Memory leak ~226 bytes per form). What it would
take 168182 Forms to crash my application with an
OutOFMemoryException.(I just show a constant increment in the first 600
forms, now I'm really trying to crash it it started with 1500000 bytes
and it's already in 2840000 bytes).
In the normal framework you were right, it behaves the same way (memory
leak).
In my scenaryo the Forms are much heavier than the ones in the
experiment and it would take less time to make it crash. I know that
the margin of showable Forms would be quite big but my application
should be running always.
This generates me some questions and doubts:

Is it possible to get to a 0 memory leak?
If one Form is deleted and all it's child references are lost shouldn't
be removed from memory by the garbage collector?
Does it happen the same with all objects and their children objects?
Do I have to override all the dispose Methods deleting all children
objects in all my objects ?

Doubt (Please correct me if i'm wrong);
In the previous example, if the DataTable used in the datagrid is
binded to it, it means that there is just a reference from a datagrid
to the dataTable (Not a copy of the datatable). Internally it can have
the structure it needs for the dataGrid visualization (Possible table
of internal objects cells) but it should belong just only to the
datagrid, and the reference to it should just be in the datagrid. So if
I would Dispose the datagrid, this internal structure should be removed
too. In my code there is a unique dataSet that is represented in N
datagrids, if all datagrids are erased why is the memory leak
increasing?
Why is it that if i remove "dataGrid1.DataSource = data.Tables[0];"
there is no memory leak?

Thanks for all the help
Jon
 
C

Chris Tacke, eMVP

Is it possible to get to a 0 memory leak?

This isn't really a "leak", it's just memory allocation not yet freed by the
GC.
If one Form is deleted and all it's child references are lost shouldn't
be removed from memory by the garbage collector?

Not necessarily. The GC doesn't run immediately when the object is
destoryed, it runs when it detects the need to do so. In fact you probably
wouldn't want it to run every time in this case becasue you'd take a severe
perf hit for 200 bytes.
Does it happen the same with all objects and their children objects?

That's the way the GC works. There are some good details in the CF team
blog if you want more info.
Do I have to override all the dispose Methods deleting all children
objects in all my objects ?

Only if you have something in your derived class that you want to
explicityly dispose.

-Chris
 
J

Jon Abaunza

First of all thank you very much for answering and clearing some of my
questions.

I have been running the application with the 200 bytes "leak" for 6
hours, expecting to see that the garbage collector would work at some
point. The result of it is that the 1MB application has become a 10 MB
application and still going on with the same increase constant.

Is it really a bug? Just in case i will wait until i get the
OutOfMemoryException.
 
G

Ginny Caughey [MVP]

Jon,

10 Mb may not be enough to trigger garbage collection. See how big it will
get - you know at what point you got the OutOfMemoryException with your
earlier tests.
 
J

Jon Abaunza

When I arrived this morning I took a look to the application and i
founf the following (I am not debugging the application because i have
to work with my desktop and another pda):
The application did not crash (no Out of memory exception).
The memory was stable 17 Mb free (I could even pop the windows start
menu).
But:
The application did not show any of the data that it was displaying
before ( A form with the the label empty and the datagrid too).

I tried to kill the process with a 3rd party application (PocketNav)
and it could not do it (it never had any problem before).
After some minutes of trying to kill it I had a Object Disposed
Exception

Thank you Chris for the article it was really interesting.
 
G

Ginny Caughey [MVP]

Thanks for reporting back, Jon. I was going to do that test myself but got
busy with some other things.
 

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