Memory Leak in managed Code

G

Guest

The following code will create memory leaks!!!


using System;
using System.Diagnostics;
using System.Data;
using System.Data.SqlClient;


namespace MemoryLeak
{
public class RGCSqlConnection:System.IDisposable
{
SqlConnection m_connSQL;
public RGCSqlConnection(string dbName, string serverName)
{
string ServerName=serverName;
string DBName=dbName;
string m_strConn="Connection Timeout=45;Integrated Security=SSPI;Persist
Security Info=False;Initial Catalog=" + dbName + ";Data Source=" +
serverName;
m_connSQL= new SqlConnection(m_strConn);
m_connSQL.Open();
}
#region IDisposable Members

public void Dispose()
{
m_connSQL.Dispose();
}

#endregion
}

public class TestClass
{
int [] a=new int [100];
~TestClass()
{
}


[STAThread]
static void Main()
{
RGCSqlConnection conn = new RGCSqlConnection("Northwind","localhost");
conn.Dispose();

TestClass t;
do
{
t=new TestClass();
}
while (true);
}
}

}
 
R

Rob Schieber

James said:
The following code will create memory leaks!!!


using System;
using System.Diagnostics;
using System.Data;
using System.Data.SqlClient;


namespace MemoryLeak
{
public class RGCSqlConnection:System.IDisposable
{
SqlConnection m_connSQL;
public RGCSqlConnection(string dbName, string serverName)
{
string ServerName=serverName;
string DBName=dbName;
string m_strConn="Connection Timeout=45;Integrated Security=SSPI;Persist
Security Info=False;Initial Catalog=" + dbName + ";Data Source=" +
serverName;
m_connSQL= new SqlConnection(m_strConn);
m_connSQL.Open();
}
#region IDisposable Members

public void Dispose()
{
m_connSQL.Dispose();
}

#endregion
}

public class TestClass
{
int [] a=new int [100];
~TestClass()
{
}


[STAThread]
static void Main()
{
RGCSqlConnection conn = new RGCSqlConnection("Northwind","localhost");
conn.Dispose();

TestClass t;
do
{
t=new TestClass();
}
while (true);
}
}

}

I don't think that you have an understanding of what a memory leak is.
Calling dispose will just mark the object from Garbage Collection, the
objects will eventially get disposed of, but not right away. To test
for a memory leak, run your app, then close it and see if resources are
returned to the heap.
 
G

Guest

I understand what you said.
Please run the code and see if the memory goes up. The memory leak problem
that I talked about happens in the while loop

-James

Rob Schieber said:
James said:
The following code will create memory leaks!!!


using System;
using System.Diagnostics;
using System.Data;
using System.Data.SqlClient;


namespace MemoryLeak
{
public class RGCSqlConnection:System.IDisposable
{
SqlConnection m_connSQL;
public RGCSqlConnection(string dbName, string serverName)
{
string ServerName=serverName;
string DBName=dbName;
string m_strConn="Connection Timeout=45;Integrated Security=SSPI;Persist
Security Info=False;Initial Catalog=" + dbName + ";Data Source=" +
serverName;
m_connSQL= new SqlConnection(m_strConn);
m_connSQL.Open();
}
#region IDisposable Members

public void Dispose()
{
m_connSQL.Dispose();
}

#endregion
}

public class TestClass
{
int [] a=new int [100];
~TestClass()
{
}


[STAThread]
static void Main()
{
RGCSqlConnection conn = new RGCSqlConnection("Northwind","localhost");
conn.Dispose();

TestClass t;
do
{
t=new TestClass();
}
while (true);
}
}

}

I don't think that you have an understanding of what a memory leak is.
Calling dispose will just mark the object from Garbage Collection, the
objects will eventially get disposed of, but not right away. To test
for a memory leak, run your app, then close it and see if resources are
returned to the heap.
 
W

Willy Denoyette [MVP]

James said:
The following code will create memory leaks!!!


using System;
using System.Diagnostics;
using System.Data;
using System.Data.SqlClient;


namespace MemoryLeak
{
public class RGCSqlConnection:System.IDisposable
{
SqlConnection m_connSQL;
public RGCSqlConnection(string dbName, string serverName)
{
string ServerName=serverName;
string DBName=dbName;
string m_strConn="Connection Timeout=45;Integrated Security=SSPI;Persist
Security Info=False;Initial Catalog=" + dbName + ";Data Source=" +
serverName;
m_connSQL= new SqlConnection(m_strConn);
m_connSQL.Open();
}
#region IDisposable Members

public void Dispose()
{
m_connSQL.Dispose();
}

#endregion
}

public class TestClass
{
int [] a=new int [100];
~TestClass()
{
}


[STAThread]
static void Main()
{
RGCSqlConnection conn = new RGCSqlConnection("Northwind","localhost");
conn.Dispose();

TestClass t;
do
{
t=new TestClass();
}
while (true);
}
}

}

No it won't, what makes you think that?
Note that you should not implement IDisposable and your TestClass should not
have a destructor (Finalize).

Willy.
 
G

Guest

That's something that I dont understand.

Why does the destructor make difference?

Change the t to be a Form class, it will cause the memory leak too.

I tried to add (Application.Doevent()) which might eventually fixed the
memory leak problem here, but in my case (which is a much larger windows form
class) will still have the memory leak problem. I really dont understand why.
All my code is managed code and only use .NET libary ( No third party libs).
It is supposed not to have memory leaks, isn't it?

-James


Willy Denoyette said:
James said:
The following code will create memory leaks!!!


using System;
using System.Diagnostics;
using System.Data;
using System.Data.SqlClient;


namespace MemoryLeak
{
public class RGCSqlConnection:System.IDisposable
{
SqlConnection m_connSQL;
public RGCSqlConnection(string dbName, string serverName)
{
string ServerName=serverName;
string DBName=dbName;
string m_strConn="Connection Timeout=45;Integrated Security=SSPI;Persist
Security Info=False;Initial Catalog=" + dbName + ";Data Source=" +
serverName;
m_connSQL= new SqlConnection(m_strConn);
m_connSQL.Open();
}
#region IDisposable Members

public void Dispose()
{
m_connSQL.Dispose();
}

#endregion
}

public class TestClass
{
int [] a=new int [100];
~TestClass()
{
}


[STAThread]
static void Main()
{
RGCSqlConnection conn = new RGCSqlConnection("Northwind","localhost");
conn.Dispose();

TestClass t;
do
{
t=new TestClass();
}
while (true);
}
}

}

No it won't, what makes you think that?
Note that you should not implement IDisposable and your TestClass should not
have a destructor (Finalize).

Willy.
 
S

Stoitcho Goutsev \(100\) [C# MVP]

James,

Memory leaks are possible in .NET and there are examples in the framework.
Memory leaks are usually associated with managed class using unmanaged
resources. However it is not that easy to spot a memory leak in .NET.

You say in the while loop memory consumptions grow. This is normal. It will
grow until the CLR decides to GC the heap. When it will happen well it
depends. It definitiely won't happen with a simple application and alot of
free memory at application's disposal.

When the GC kicks off again the memory consuption may not go down
completely. Objects that have finalizers it takes 2 GC cycles to be
collected it also depends on object generation, etc. Thre are more to that.
SqlConnections are kept in a pool so even if you close a connection the
object stays pooled.

It is not that easy to localize a memory leak. More often memory leaks
happen because programmers keep some hidden and forgotten references to
unused objects than memory leaks due to bugs in the frameworks.


--

Stoitcho Goutsev (100) [C# MVP]

James said:
I understand what you said.
Please run the code and see if the memory goes up. The memory leak problem
that I talked about happens in the while loop

-James

Rob Schieber said:
James said:
The following code will create memory leaks!!!


using System;
using System.Diagnostics;
using System.Data;
using System.Data.SqlClient;


namespace MemoryLeak
{
public class RGCSqlConnection:System.IDisposable
{
SqlConnection m_connSQL;
public RGCSqlConnection(string dbName, string serverName)
{
string ServerName=serverName;
string DBName=dbName;
string m_strConn="Connection Timeout=45;Integrated
Security=SSPI;Persist
Security Info=False;Initial Catalog=" + dbName + ";Data Source=" +
serverName;
m_connSQL= new SqlConnection(m_strConn);
m_connSQL.Open();
}
#region IDisposable Members

public void Dispose()
{
m_connSQL.Dispose();
}

#endregion
}

public class TestClass
{
int [] a=new int [100];
~TestClass()
{
}


[STAThread]
static void Main()
{
RGCSqlConnection conn = new RGCSqlConnection("Northwind","localhost");
conn.Dispose();

TestClass t;
do
{
t=new TestClass();
}
while (true);
}
}

}

I don't think that you have an understanding of what a memory leak is.
Calling dispose will just mark the object from Garbage Collection, the
objects will eventially get disposed of, but not right away. To test
for a memory leak, run your app, then close it and see if resources are
returned to the heap.
 
M

Mythran

James said:
The following code will create memory leaks!!!

What you understand to be a memory leak is in fact not a memory leak. The
..Net framework gives excess memory allocation to the requesting application
if the system can spare it. Because it is so expensive to allocate more and
more memory, the system is generous when an application asks the system for
some more memory. If the application requests a little bit more (say 1 MB),
the system may give 5 or 10 MB, just so the application doesn't have to
request memory so much.

When the system starts to get low on resources, the application will return
memory back to the system. Here is a test to see a work-around for your
problem. After the loop finishes, look at how much memory your application
is using then minimize the application. Does the memory change? :)

Mythran
 
G

Guest

Thanks for replying.
I understand the GC will collect the garbic at a certain time indefintely.
However, if you ran the code, you will see the memory is never going down,
which I assume is a memory leak. In my real application, it is a much more
complex windows form, but the main function is exactly what i wrote in the
post.

-James


Stoitcho Goutsev (100) said:
James,

Memory leaks are possible in .NET and there are examples in the framework.
Memory leaks are usually associated with managed class using unmanaged
resources. However it is not that easy to spot a memory leak in .NET.

You say in the while loop memory consumptions grow. This is normal. It will
grow until the CLR decides to GC the heap. When it will happen well it
depends. It definitiely won't happen with a simple application and alot of
free memory at application's disposal.

When the GC kicks off again the memory consuption may not go down
completely. Objects that have finalizers it takes 2 GC cycles to be
collected it also depends on object generation, etc. Thre are more to that.
SqlConnections are kept in a pool so even if you close a connection the
object stays pooled.

It is not that easy to localize a memory leak. More often memory leaks
happen because programmers keep some hidden and forgotten references to
unused objects than memory leaks due to bugs in the frameworks.


--

Stoitcho Goutsev (100) [C# MVP]

James said:
I understand what you said.
Please run the code and see if the memory goes up. The memory leak problem
that I talked about happens in the while loop

-James

Rob Schieber said:
James wrote:
The following code will create memory leaks!!!


using System;
using System.Diagnostics;
using System.Data;
using System.Data.SqlClient;


namespace MemoryLeak
{
public class RGCSqlConnection:System.IDisposable
{
SqlConnection m_connSQL;
public RGCSqlConnection(string dbName, string serverName)
{
string ServerName=serverName;
string DBName=dbName;
string m_strConn="Connection Timeout=45;Integrated
Security=SSPI;Persist
Security Info=False;Initial Catalog=" + dbName + ";Data Source=" +
serverName;
m_connSQL= new SqlConnection(m_strConn);
m_connSQL.Open();
}
#region IDisposable Members

public void Dispose()
{
m_connSQL.Dispose();
}

#endregion
}

public class TestClass
{
int [] a=new int [100];
~TestClass()
{
}


[STAThread]
static void Main()
{
RGCSqlConnection conn = new RGCSqlConnection("Northwind","localhost");
conn.Dispose();

TestClass t;
do
{
t=new TestClass();
}
while (true);
}
}

}


I don't think that you have an understanding of what a memory leak is.
Calling dispose will just mark the object from Garbage Collection, the
objects will eventially get disposed of, but not right away. To test
for a memory leak, run your app, then close it and see if resources are
returned to the heap.
 
S

Stoitcho Goutsev \(100\) [C# MVP]

That's what I was trying to explain the fact that memory grows doesn
ncessary means there is a memory leak.

Your test I see has nothing to do with the Connection class you've created.
Your test is basically a class that upon creation allocates array of 100
intgers.

And in a loop you create isntances of this class - 100 integers every time.
You say that there is a memory leak because everytime you create a new
object of this TestClass the memory consumption grows. Is that correct?

What do you expect to happen? What is the correct behavior? On every loop
you get more memory, why shouldn't memory grow?

BTW, you made it even harder for the GC declaring this empty destructor
(finalizer).


--

Stoitcho Goutsev (100) [C# MVP]

James said:
Thanks for replying.
I understand the GC will collect the garbic at a certain time indefintely.
However, if you ran the code, you will see the memory is never going down,
which I assume is a memory leak. In my real application, it is a much more
complex windows form, but the main function is exactly what i wrote in the
post.

-James


Stoitcho Goutsev (100) said:
James,

Memory leaks are possible in .NET and there are examples in the
framework.
Memory leaks are usually associated with managed class using unmanaged
resources. However it is not that easy to spot a memory leak in .NET.

You say in the while loop memory consumptions grow. This is normal. It
will
grow until the CLR decides to GC the heap. When it will happen well it
depends. It definitiely won't happen with a simple application and alot
of
free memory at application's disposal.

When the GC kicks off again the memory consuption may not go down
completely. Objects that have finalizers it takes 2 GC cycles to be
collected it also depends on object generation, etc. Thre are more to
that.
SqlConnections are kept in a pool so even if you close a connection the
object stays pooled.

It is not that easy to localize a memory leak. More often memory leaks
happen because programmers keep some hidden and forgotten references to
unused objects than memory leaks due to bugs in the frameworks.


--

Stoitcho Goutsev (100) [C# MVP]

James said:
I understand what you said.
Please run the code and see if the memory goes up. The memory leak
problem
that I talked about happens in the while loop

-James

:

James wrote:
The following code will create memory leaks!!!


using System;
using System.Diagnostics;
using System.Data;
using System.Data.SqlClient;


namespace MemoryLeak
{
public class RGCSqlConnection:System.IDisposable
{
SqlConnection m_connSQL;
public RGCSqlConnection(string dbName, string serverName)
{
string ServerName=serverName;
string DBName=dbName;
string m_strConn="Connection Timeout=45;Integrated
Security=SSPI;Persist
Security Info=False;Initial Catalog=" + dbName + ";Data Source=" +
serverName;
m_connSQL= new SqlConnection(m_strConn);
m_connSQL.Open();
}
#region IDisposable Members

public void Dispose()
{
m_connSQL.Dispose();
}

#endregion
}

public class TestClass
{
int [] a=new int [100];
~TestClass()
{
}


[STAThread]
static void Main()
{
RGCSqlConnection conn = new
RGCSqlConnection("Northwind","localhost");
conn.Dispose();

TestClass t;
do
{
t=new TestClass();
}
while (true);
}
}

}


I don't think that you have an understanding of what a memory leak is.
Calling dispose will just mark the object from Garbage Collection, the
objects will eventially get disposed of, but not right away. To test
for a memory leak, run your app, then close it and see if resources
are
returned to the heap.
 
W

Willy Denoyette [MVP]

James said:
That's something that I dont understand.

Why does the destructor make difference?

It has nothing to do with your so called memory leak. However, It's not
needed, worse it creates some overhead as each object must be registered as
a finalizable object, so it needs two GC runs before getting collected.

Change the t to be a Form class, it will cause the memory leak too.

I tried to add (Application.Doevent()) which might eventually fixed the
memory leak problem here, but in my case (which is a much larger windows
form
class) will still have the memory leak problem. I really dont understand
why.
All my code is managed code and only use .NET libary ( No third party
libs).
It is supposed not to have memory leaks, isn't it?

-James

When I run the code you posted (both on v1.1 4322 SP1 and v2 of the
framework), the "working set" stays the same ~12MB and the private bytes
stay at ~9MB. That means there is no leak at all.
I realy don't know what values you are measuring and what version of the
framework you are running, so it would help us if you post your memory
counter values.


Willy.
 
J

Jon Skeet [C# MVP]

James said:
That's something that I dont understand.

Why does the destructor make difference?

Because that means the finalizer needs to be run before the instance
can be garbage collected.
Change the t to be a Form class, it will cause the memory leak too.

I tried to add (Application.Doevent()) which might eventually fixed the
memory leak problem here, but in my case (which is a much larger windows form
class) will still have the memory leak problem. I really dont understand why.
All my code is managed code and only use .NET libary ( No third party libs).
It is supposed not to have memory leaks, isn't it?

I strongly suspect you're running into an interesting problem with
STAThreads. I have a sneaking suspicion that finalizers for objects
created on STA threads are actually executed within that thread -
otherwise you'd have difficulty with things which should only ever be
accessed on that thread.

Now, normally that's not a problem, because you'd normally create a UI
thread and let it do its message pumping appropriately. In your case,
you're blocking the thread, so it can't run the finalizer.

This is all only a suspicion, but given what you say about
Application.DoEvents (which would allow the UI to run the message pump
and thus the finalizers) it sounds likely.

Basically, you shouldn't block your thread - or you shouldn't be using
STAThread in the first place. Frankly I've always thought it a shame
that the COM apartments have inveigled their way into .NET, but I guess
that's the price you pay for pretty good COM interop.
 
J

Jon Skeet [C# MVP]

Willy Denoyette said:
It has nothing to do with your so called memory leak. However, It's not
needed, worse it creates some overhead as each object must be registered as
a finalizable object, so it needs two GC runs before getting collected.

Actually, it has everything to do with the memory leak, if I understand
what's going on. I believe that at least one finalizer (probably
something to do with SQL Server) is blocked, trying to get back onto
the main thread (which is busy) and so none of the other finalizers (of
TestClass) are executing - and thus the instances aren't getting
collected.

When I run the code you posted (both on v1.1 4322 SP1 and v2 of the
framework), the "working set" stays the same ~12MB and the private bytes
stay at ~9MB. That means there is no leak at all.

Interestingly, I see no leak with 2.0, but I *do* see a leak with 1.1.
 
J

Jon Skeet [C# MVP]

Mythran said:
What you understand to be a memory leak is in fact not a memory leak.

It sort of is, actually. The finalizer thread is getting blocked, so
none of the test class instances are getting collected. I believe the
finalizer thread is waiting to execute code on the STAThread, which it
can't do because that thread is always busy.

It's worth noting that any of the following remove the problem:

1) Running under .NET 2.0
2) Removing the creation/disposal of the connection
3) Removing the STAThread attribute
4) Removing the finalizer from TestClass
 
I

Ignacio Machin \( .NET/ C# MVP \)

Hi,

When I run the code you posted (both on v1.1 4322 SP1 and v2 of the
framework), the "working set" stays the same ~12MB and the private bytes
stay at ~9MB. That means there is no leak at all.
I realy don't know what values you are measuring and what version of the
framework you are running, so it would help us if you post your memory
counter values.


I do see the same thing than James, in my machine V1.1 XP sp2 1GB ram the
memory use increase until all memory is used. It does return to normality
when the program is stopped.

The wierd thing is that the destructor is never called
 
J

Jon Skeet [C# MVP]

Jon Skeet said:
I strongly suspect you're running into an interesting problem with
STAThreads. I have a sneaking suspicion that finalizers for objects
created on STA threads are actually executed within that thread -
otherwise you'd have difficulty with things which should only ever be
accessed on that thread.

Now, normally that's not a problem, because you'd normally create a UI
thread and let it do its message pumping appropriately. In your case,
you're blocking the thread, so it can't run the finalizer.

<snip>

I've finally found the blog post I was looking for which describes
this:

http://blogs.msdn.com/cbrumme/archive/2004/02/02/66219.aspx

It's long and complicated, but this quote is significant:

<quote>
Assume for a moment that a Console application also puts its main
thread into an STA. If that main thread creates any COM objects via
COM Interop, and if those COM objects are ThreadingModel=Main or Both,
then the application better be pumping. If it fails to pump, we=3Fll
have exactly the same situation with our server running ASP
compatibility mode. The Finalizer thread won=3Ft be able to marshal
calls into the STA to Release any pUnks.
</quote>

I strongly suspect that the SQL client code is creating COM objects
with the above threading models, hence the problem.

Quite how it's been fixed in 2.0, I don't know...
 
W

Willy Denoyette [MVP]

Jon Skeet said:
Actually, it has everything to do with the memory leak, if I understand
what's going on. I believe that at least one finalizer (probably
something to do with SQL Server) is blocked, trying to get back onto
the main thread (which is busy) and so none of the other finalizers (of
TestClass) are executing - and thus the instances aren't getting
collected.



Interestingly, I see no leak with 2.0, but I *do* see a leak with 1.1.

I'm running this on v1.1 and v2.0 without any leak at all.
But I do have a connection with an SQL server on another Server using TCP.
I guess that the OP's issue relates to the local connection using shared
memory IPC mechanism.
That would mean that the Finalizer thread is blocked and as such isn't able
to run the finalize methods of the TestClass instances, effectively
preventing the GC to collect the instances.
I can't try this with a local instance of SQL in order to see if this
relates to v1.1, but I trust your findings and think this is a bug in v1.1
SqlClient namespace, probaly only using shared memory as IPC channel. Maybe
one of you could verify this and try with named pipe and TCP channels.

Willy.
 
W

Willy Denoyette [MVP]

Ignacio Machin ( .NET/ C# MVP ) said:
Hi,




I do see the same thing than James, in my machine V1.1 XP sp2 1GB ram the
memory use increase until all memory is used. It does return to normality
when the program is stopped.

The wierd thing is that the destructor is never called

See my reply to Jon's post.

Willy.
 
I

Ignacio Machin \( .NET/ C# MVP \)

Hi Willy,

I'm running this on v1.1 and v2.0 without any leak at all.
But I do have a connection with an SQL server on another Server using TCP.
I guess that the OP's issue relates to the local connection using shared
memory IPC mechanism.
That would mean that the Finalizer thread is blocked and as such isn't
able to run the finalize methods of the TestClass instances, effectively
preventing the GC to collect the instances.
I can't try this with a local instance of SQL in order to see if this
relates to v1.1, but I trust your findings and think this is a bug in v1.1
SqlClient namespace, probaly only using shared memory as IPC channel.
Maybe one of you could verify this and try with named pipe and TCP
channels.

Willy.
I tested this with a remote server and the same thing happened.
 

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