.NET Forum / Languages / C# / December 2005
Memory Leak in managed Code
|
|
Thread rating:  |
James - 23 Dec 2005 13:59 GMT 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); } }
}
Rob Schieber - 23 Dec 2005 15:06 GMT > The following code will create memory leaks!!! > [quoted text clipped - 51 lines] > > } 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.
 Signature Rob Schieber
James - 23 Dec 2005 15:15 GMT 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
> > The following code will create memory leaks!!! > > [quoted text clipped - 57 lines] > for a memory leak, run your app, then close it and see if resources are > returned to the heap. Stoitcho Goutsev (100) [C# MVP] - 23 Dec 2005 16:22 GMT 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.
 Signature Stoitcho Goutsev (100) [C# MVP]
>I understand what you said. > Please run the code and see if the memory goes up. The memory leak problem [quoted text clipped - 64 lines] >> for a memory leak, run your app, then close it and see if resources are >> returned to the heap. James - 23 Dec 2005 16:35 GMT 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
> James, > [quoted text clipped - 85 lines] > >> for a memory leak, run your app, then close it and see if resources are > >> returned to the heap. Stoitcho Goutsev (100) [C# MVP] - 23 Dec 2005 17:02 GMT 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).
 Signature Stoitcho Goutsev (100) [C# MVP]
> Thanks for replying. > I understand the GC will collect the garbic at a certain time indefintely. [quoted text clipped - 101 lines] >> >> are >> >> returned to the heap. Willy Denoyette [MVP] - 23 Dec 2005 15:19 GMT > The following code will create memory leaks!!! > [quoted text clipped - 51 lines] > > } 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.
James - 23 Dec 2005 15:49 GMT 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
> > The following code will create memory leaks!!! > > [quoted text clipped - 57 lines] > > Willy. Willy Denoyette [MVP] - 23 Dec 2005 17:22 GMT > 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. > [quoted text clipped - 8 lines] > > -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.
Jon Skeet [C# MVP] - 23 Dec 2005 18:09 GMT > > That's something that I dont understand. > > [quoted text clipped - 3 lines] > 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.
<snip>
> 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.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Willy Denoyette [MVP] - 23 Dec 2005 19:20 GMT >> > That's something that I dont understand. >> > [quoted text clipped - 19 lines] > > 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.
Ignacio Machin ( .NET/ C# MVP ) - 23 Dec 2005 19:27 GMT 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. [quoted text clipped - 10 lines] > > Willy. I tested this with a remote server and the same thing happened.
 Signature Ignacio Machin, ignacio.machin AT dot.state.fl.us Florida Department Of Transportation
Willy Denoyette [MVP] - 23 Dec 2005 19:39 GMT > Hi Willy, > [quoted text clipped - 15 lines] >> > I tested this with a remote server and the same thing happened. Ignacio,
You mean it's leaking, right? Weird, I run this on v1.1.4322 SP1 XP SP2 with SQL2005 as backend on W2K3. I'm also using SSPI as security protocol. I will double check to see if I'm not mixing v1.1 and v2 stuff.
Willy.
Willy Denoyette [MVP] - 23 Dec 2005 19:43 GMT > Hi Willy, > [quoted text clipped - 15 lines] >> > I tested this with a remote server and the same thing happened. Ok, guys I'm also experiencing the leak on 1.1.4233, must have made a mistake when compiling. Sorry about the confusions.
Willy.
Ignacio Machin ( .NET/ C# MVP ) - 23 Dec 2005 18:16 GMT 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
 Signature Ignacio Machin, ignacio.machin AT dot.state.fl.us Florida Department Of Transportation
Willy Denoyette [MVP] - 23 Dec 2005 19:21 GMT > Hi, > [quoted text clipped - 10 lines] > > The wierd thing is that the destructor is never called See my reply to Jon's post.
Willy.
Jon Skeet [C# MVP] - 23 Dec 2005 18:06 GMT > 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. > [quoted text clipped - 3 lines] > 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.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Jon Skeet [C# MVP] - 23 Dec 2005 18:53 GMT > I strongly suspect you're running into an interesting problem with > STAThreads. I have a sneaking suspicion that finalizers for objects [quoted text clipped - 5 lines] > 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...
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Mythran - 23 Dec 2005 16:24 GMT > 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
Jon Skeet [C# MVP] - 23 Dec 2005 18:13 GMT > > The following code will create memory leaks!!! > > 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
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Willy Denoyette [MVP] - 23 Dec 2005 19:34 GMT >> > The following code will create memory leaks!!! >> [quoted text clipped - 11 lines] > 3) Removing the STAThread attribute > 4) Removing the finalizer from TestClass Jon,
Could add a call To WaitForPendingFinalizers() after the Dispose call, with the STAThread set and see what happens? I'm looking at the v1.1 sources and found a COM dependency, this part is somewhat complex code so before I go deeper I would see this confirmed. Why? Well, it would confirm that the finalizer is blocked trying to finalize the RCW on an STA thread that doesn't pump messages! (which is real bad). That would also explain why there is no issue on an MTA thread.
Willy.
Willy Denoyette [MVP] - 23 Dec 2005 19:57 GMT >> > The following code will create memory leaks!!! >> [quoted text clipped - 11 lines] > 3) Removing the STAThread attribute > 4) Removing the finalizer from TestClass Ok, no need to check any further. The 'leak' is due to a blocked finalizer thread because of a COM dependency is the SqlClient namespace. What you should do is call GC.WaitForPendingFinalizers() in order to pump the message queue in an STA thread. Console applications don't pump messages (actually they have no message queue), that's why I insist you should never apply STAThread in a console (or console like) application. This is an issue I posted several times about before, and the rule is simple, if you really needs an STA thread for a console application you should must pump messages. To help you with this the framework provides API's like WaitForPendingFinalizers(), WaitOne() and a couple of others that do pump the queue (for COM messages only). But again, if you don't need an STA thread, stay away from it, they are only meant for Windows Forms applications UI threads.
Willy.
Roger - 23 Dec 2005 19:19 GMT This is the difference between Reserved and Commited memory. This is a Windows feature, which has been around before .NET. Roger
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/memory/base/res erving_and_committing_memory.asp
> 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 [quoted text clipped - 10 lines] > > Mythran Chris R. Timmons - 23 Dec 2005 18:14 GMT James,
These MSDN articles give a detailed description of how the .Net garbage collector works:
http://msdn.microsoft.com/msdnmag/issues/1100/GCI/TOC.ASP
http://msdn.microsoft.com/msdnmag/issues/1200/GCI2/TOC.ASP
http://msdn.microsoft.com/library/en-us/dndotnet/html/dotnetgcbasics.asp
http://msdn.microsoft.com/library/en-us/dndotnet/html/dotnetperftechs.asp
http://msdn.microsoft.com/msdnmag/issues/03/01/NETProfilerAPI/TOC.ASP
 Signature Hope this helps.
Chris. ------------- C.R. Timmons Consulting, Inc. http://www.crtimmonsinc.com/
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|