views:

176

answers:

3

Recently discovered that the variables inside ToGadget, and presumably the delegate as well, weren't getting garbage collected. Can anyone see why .NET holds a reference to this? Seems that the delegate and all would be marked for garbage collection after Foo ends. Literally saw *B*illions in memory after dumping the heap.

Note: 'result.Things' is a List<Gadget> () and Converter is a System delegate.


        public Blah Foo()
        {
                var result = new Blah();
                result.Things = this.Things.ConvertAll((new Converter(ToGadget)));
                return result;
        }
        .................
        public static Gadget ToGadget(Widget w)
        {
            return new Gadget(w);
        }

Update: changing the 'ConvertAll' to this cleans up the delegates and corresponding object references. This suggests to me that either List<> ConvertAll is somehow holding on to the delegate or I don't understand how these things are garbage collected.


            foreach (var t in this.Things)
            {
                result.Things.Add(ToGadget(t));         
            }
+2  A: 

It looks like your function is set up to return the new Blah(). Is it actually being returned in your code? I see in the piece you posted that it is not. If that is the case, then the new Blah() would have a scope outside of Foo and it may be the calling function that is actually holding the references in scope. Also, you're creating new Gadget() as well. Depending on how many Blahs to Gadgets you have, you could be exponentially filling your memory as the Gadgets will be scoped with the Blahs which are then held in scope beyond Foo.

Whether I'm right or wrong, this possibility was kinda funny to type.

Joel Etherton
+2  A: 

There is one major flaw in your question, which may be the cause of confusion:

Seems that the delegate and all would be marked for garbage collection after Foo ends.

The CLR doesn't "mark items" for collection at the end of a routine. Rather, once that routine ends, there is no longer an (active) reference to any of the items referenced in your delegate. At that point, they are what is refered to as "unrooted".

Later, when the CLR determines that there is a certain amount of memory pressure, the garbage collector will execute. It will search through and find all unrooted elements, and potentially collect them.

The important distinction here is that the timing is not something that can be predicted. The objects may never be collected until your program ends, or they may get collected right away. It's up to the system to determine when it will collect. This doesn't happen when Foo ends - but rather at some unknown amount of time after Foo ends.


Edit:

This is actually directly addressing your question, btw. You can see if this is the issue by forcing a garbage collection. Just add, after your call to Foo, a call to:

GC.Collect();
GC.WaitForPendingFinalizers();

Then do your checking of the CLR's heap. At this point, if you're still getting objects in the heap, it's because the objects are still being rooted by something. Your simplified example doesn't show this happening, but as this is a very simplified example, it's difficult to determine where this would happen. (Note: I don't recommend keeping this in your code, if this is the case. Calling GC.Collect() manually is almost always a bad idea...)

Reed Copsey
Thanks for the clarification, but not really helpful in determining why there are sooooo many objects in memory
Trent
Mmm - This directly addresses that issue, actually. I'll edit to be more clear...
Reed Copsey
@Trent: Read my update, and see if it still doesn't make sense to you...
Reed Copsey
There are so many objects in memory because the garbage collector hasn't felt the need to do a full pass and clean them up yet.
Anon.
@Anon: That's exactly my thought. I think this may be a misunderstanding of how the GC works, not that there is a problem with the objects...
Reed Copsey
Interesting thought fellas. But why would the garbage collector not see a need to do a pass when you have a 9GB process? Seems pretty unlikely, but I could be wrong.
Trent
@Trent: The GC won't necessarily do a pass with a 9GB process if you have much more than 9GB of memory, and memory pressure isn't an issue. GC collections take time, so the GC doesn't run continually - in fact, the less the GC runs, the better (provided you don't run out of memory)!
Reed Copsey
Agreed. Maybe I should rescope my question. Please read the update above.
Trent
+5  A: 

Use a memory profiler.

You can ask on StackOverflow all day and get a bunch of educated guesses, or you can slap a memory profiler on your application and immediately see what is rooted and what is garbage. There are tools available that are built specifically to solve your exact problem quickly and easily. Use them!

Eric Lippert
Amen. Everything else is guesswork.
spender
Wouldn't the fact that he's "dumping the heap" and looking at specific instances suggest he's doing some form of memory profiling already? I read his question more as "Why is my profiling telling me I have XXXX? This doesn't make sense."
Reed Copsey
@Eric, did you read the post?
Trent
I've looked at plenty of heap dumps that were made without profilers, so, no, that doesn't suggest to me that profiling is being done.
Eric Lippert