3

I'm writing an application where after a user uses it - he can 'reset' it and use it again. The actual use creates some objects including Controls.

I checked the memory usage using Process Explorer and discovered that when the app is used - there's a rise in memory-use, but when the app is 'reset' - the memory-use hardly goes down.

So at the 'reset' I Dispose of all of the Controls recursively, and even added a GC.Collect(); to boot. But Process Explorer -> (app's-) Properties -> .Net CLR Memory still shows lots of stuff on the heaps and lots of memory used.

Is this (necessarily) a memory leak? And how come the forced GC doesn't help?

EDIT:

I added the GC.Collect(); just for debugging - for determining if there is a leak. I'm not worried about a 'temporary leak' until the GC works, I'm just trying to find if there are objects that never go out of scope (though I haven't found them yet) or if it's just a matter of time, and the memory will be reclaimed.

ispiro
  • 26,556
  • 38
  • 136
  • 291
  • 1
    Did you try to wait for finalizers? `GC.WaitForPendingFinalizers()` – OzrenTkalcecKrznaric Oct 30 '12 at 21:36
  • 1
    Does it go up again after reuse? It might just be a bunch of assemblies that you've picked up by creating a bunch of controls that required new libraries. – tmesser Oct 30 '12 at 21:36
  • 2
    Sounds like pre-emptive optimization. What PROBLEM are you describing? Using memory typically isn't a problem. – Erik Philips Oct 30 '12 at 21:38
  • The crucial question is whether memory usage _keeps_ rising as you run the application again and again. An increase in memory usage during the _first_ run is to be expected because various static initializations in behind-the-scenes code will happen the first time the relevant code paths are executed. – hmakholm left over Monica Oct 30 '12 at 21:41
  • @YYY Yes. It continues to rise after a 'reset'. – ispiro Oct 30 '12 at 21:43
  • @HenningMakholm It does. (-continue to rise.) – ispiro Oct 30 '12 at 21:44
  • 1
    @ErikPhilips Memory leaks _are_ a problem. – ispiro Oct 30 '12 at 21:44
  • @ErikPhilips: There's nothing preemptive about it if the memory actually is never being reclaimed or if it is not being reclaimed fast enough to suit the use case of this application. Now, whether or not that's the case I can't say unless the OP adds more info. – Ed S. Oct 30 '12 at 21:48
  • *"I added the GC.Collect(); just for debugging - for determining if there is a leak."* - I'm not sure how you intend on determining whether or not you have a leak by calling GC.Collect. Better to use a tool to see which objects are hanging around if you can't debug the problem mentally. – Ed S. Oct 30 '12 at 21:59
  • @EdS. I thought it would force a GC and I'll be able to see if there's anything left as a permanent leak. – ispiro Oct 30 '12 at 22:01
  • @ispiro: If `GC.Collect` Guaranteed a full GC pass (i.e., gen 2 and LOH) then it wouldn't be a bad idea for a quick test. However, it does not, so it doesn't tell you much. You're better off using a tool to show you where your memory is going and which objects (if any) are hanging around after they should have gone out of scope. – Ed S. Oct 30 '12 at 22:02

3 Answers3

5

Is this (necessarily) a memory leak?

Well... yes and no. It's for the most part impossible to have a true memory leak in a C# app, and even the types which utilize native resources under the hood will clean up after themselves in their finalizer if Dispose() was never called.

Regardless though, it is possible to have stale references and native resources which take too long to clean up (or poorly written native functions which leak themselves), which has the same practical result as a true memory leak would in an unmanaged language.

And how come the forced GC doesn't help?

You gave the GC a suggestion and it chose not to take it. GC.Collect() doesn't force the GC to clean up everything and the documentation tells you as much. Secondly, is this memory usage actually causing a problem? How much memory is being consumed? Does it ever get freed? If it is just a small amount then you're probably worrying about nothing, let the GC do its job.

Without seeing your code all I can do is to guess. There are a couple of ways in which memory pressure can build up in a C# app.

  1. Stale references. Lists of references to "dead" objects that you never clear. Event handlers can cause this as well. For example, a static event should always be unsubscribed to because the event delegate holds a reference to the subscriber.

    So, if the subscriber goes out of scope without unsubscribing to the event you essentially have a memory leak because there is always a valid reference to these "dead" objects. Note that the typical use case for events does not cause this to occur because the parent (event publisher) typically goes out of scope when you need it to.

  2. Not disposing of objects which implement IDisposable. Sure, they probably clean up after themselves in their finalizer (all .NET IDisposable types do and the rest should), but finalizers run at indeterminate times, making the whole thing unpredictable. Make sure to call Dispose() (use a using statement when possible) asap.

  3. Large object heap fragmentation. If you are allocating a lot of "large" objects (objects that are > 85,000 bytes) then it is possible for this segment of memory to become fragmented. This memory is not cleaned up as often as 0th and 1st gen objects allocated on the standard heap are.

The .NET GC is complicated and usually very good at its job. Sometimes it is acceptable to call GC.Collect, but you should understand when it is a good idea and also realize that it may or may not do what you want it to. For example, if you create a large amount of short lived objects.

I have had good experiences using RedGate's .NET memory profiler when I had exhausted other avenues. I don't work for them nor am I affiliated with them in any way just so you know, and they also have a free trial. Worth a look.

Community
  • 1
  • 1
Ed S.
  • 122,712
  • 22
  • 185
  • 265
  • Thanks for the detailed answer. I added an edit to the question - It's exactly those 'Stale references' which I'm worried about. – ispiro Oct 30 '12 at 21:59
  • @ispiro: Well, it's hard to say without seeing your code, but I realize there may be too much of it to post. You can use the tool I recommend or you can try to visualize what is going on (I know, easier said than done). Just think about it; where are references being stored (don't forget events/handlers) and am I failing to call Dispose() on any objects which implement IDisposable? – Ed S. Oct 30 '12 at 22:01
  • We use memory dumps and windbg to identify memory leaks. windbg is not user friendly but it is free and you'll have to learn a lot of interesting stuff to be able to use it. – Puterdo Borato Oct 30 '12 at 22:08
  • @ispiro: Did you figure it out? I'm curious. – Ed S. Oct 30 '12 at 22:33
2

To find leaking memory, use a tool designed to do just that:

What strategies and tools are useful for finding memory leaks in .NET?

The garbage collector in the .NET Framework does not free memory until memory pressure rises above some heuristic.

Community
  • 1
  • 1
Tergiver
  • 14,171
  • 3
  • 41
  • 68
1

As others have noted GC.Collect() alone doesn't ensure a GC, one thing you can try is the GetTotalMemory method which takes a boolean parameter allowing you to force a full GC e.g.

GC.GetTotalMemory(true);

This will force a total collection and then gives you an approximation of the amount of managed memory in bytes allocated.

Like the other methods shown this does not guarantee that a collection happens (see the remarks section of the MSDN documentation) and obviously only eligible things can be collected anyway.

RobV
  • 28,022
  • 11
  • 77
  • 119
  • I didn't understand - so _does_ it force a GC, or doesn't it? – ispiro Oct 30 '12 at 21:49
  • 1
    It signals that you'd like it to wait for a GC to happen but it only waits for some period of time, see the Remarks section of the relevant documentation – RobV Oct 30 '12 at 21:50