views:

271

answers:

4

I've read some docs about the .NET Garbage Collector but i still have some doubts (examples in C#):

1)Does GC.Collect() call a partial or a full collection? 2)Does a partial collection block the execution of the "victim" application? If yes.. then i suppose this is a very "light" things to do since i'm running a game server that uses 2-3GB of memory and i "never" have execution stops (or i can't see them..). 3)I've read about GC roots but still can't understand how exactly they works. Suppose that this is the code (C#):

MyClass1:

[...] 
public List<MyClass2> classList = new List<MyClass2>(); 
[...]

Main:

main()
    {
     MyClass1 a = new MyClass1();
     MyClass2 b = new MyClass2();
     a.classList.Add(b);

     b = null;

     DoSomeLongWork();
    }

Will b ever be eligible to be garbage collected(before the DoSomeLongWork finishes)? The reference to b that classList contains, can it be considered a root? Or a root is only the first reference to the instance? (i mean, b is the root reference because the instantiation happens there).

+3  A: 
  1. GC.Collect() does a full collection. That's why it's not a good idea to call it yourself, as it can prematurely promote objects up a generation

  2. AFAIK, not really. The GC blocks for so little time as to be inconsequential.

  3. A stack GC root is an object that is currently being referenced by a method on the stack. Note that the behaviour is different between debug and release builds; on debug builds all variables in a method are kept alive until the method returns (so you can see their value in the debug window). On release builds the CLR keeps track of where method variables are used and the GC can remove objects that are referenced by the currently executing method but aren't used in the section of the method that is still to execute.

So, in your example, both a and b are not referenced again after the DoSomeLongWork() call, so in release builds, during that method execution they would both be eligible for collection. In debug builds they would hang around until the main() method returns.

thecoop
About your third answer, why are you saying "if the b = null; line was not there"? If the line WAS there the things you described (in release mode) will not happen? And i still can't understand if the reference in the List works as a valid reference or if i do b = null and then the Garbage Collector runs i would find that the List reference points to null.
Smjert
Sorry, I edited my post. The reference in the list is not counted as a root because you can only get to it through `a` - and that object is eligible for collection, so the list is eligible (if nothing else references it), so `b` is eligible.
thecoop
a and b are not referenced again *before* DoSomeLongWork. They'll both be garbage collected if a GC happens while DoSomeLongWork is running. This cannot cause a problem because there is no reference.
Hans Passant
I think i've wrote a partially "wrong" example in the third question.Suppose that b = null appears in DoSomeLongWork(), a and b are properties of some class and in that method there's code that refers to a and b, if i want that the GC frees b memory it's enough to do b = null or, as someone says in the other answers, i have to remove the reference also in the List?
Smjert
I ask also for an advice (hoping that i'm not going too offtopic): we call GC.Collect() for our game server when it uses more than 3.6GB since we fear the 4GB per application limit and because we want to know when a full collection happens (we send an in-game message).Is this correct? There's another better way to do it? Finally, is the 4GB limit per application really true or is per .Net object? (like a List that uses 4GB). If it's true there's a way to use more than 4GB memory?Sorry about all this questions, but they are all linked, if i'm going offtopic i will find answers somewhere else
Smjert
+2  A: 

Garbage collection is automatic. You don't have to interfere unless you are dealing with unmanaged resources. Garbage gets collected whenever the object is going out of scope; and at specific intervals whenever garbage-collector deems necessary - for instance, OS requires memory. This means there is no guarantee on how soon that will be, but your application will not run out of memory before memory from those objects is reclaimed.

Will b ever be eligible to be garbage collected(before the DoSomeLongWork finishes)?

Yes, whenever garbage collector finds it necessary.

Checkout Garbage Collector Basics and Performance Hints

KMan
+3  A: 
  1. There are a couple of overloads for GC.Collect. The one without any parameters does a full collect. But keep in mind that it is rarely a good idea to call GC explicitly.

  2. Garbage collections occurs within a managed application. The GC may have to suspend all managed threads in that process in order to compact the heap. It doesn't affect other processes on the system if that is what you're asking. For most applications it is usually not an issue, but you may experience performance related problems due to frequent collects. There are a couple of relevant performance counters (% time in GC, etc.) that you can use to monitor how the GC is performing.

  3. Think of a root as a valid reference. In your example the instance of MyClass2 will not be collected if there are still references to it. I.e. if the instance pointed to by a is still rooted so will your instance of MyClass2. Also, keep in mind that GC is much more aggressive in release mode builds than debug builds.

Brian Rasmussen
A: 

Will b ever be eligible to be garbage collected (before the DoSomeLongWork finishes)?

Potentially yes, provided a and b are expelled from the set of global roots by the compiler (i.e. not kept on the stack) then they can be reclaimed by a collection during DoSomeLongWork.

I have found cases where .NET reclaims happily but Mono leaks memory.

The reference to b that classList contains, can it be considered a root?

Whether or not b will be turned into a global root depends entirely upon the compiler.

  • A toy compiler might push references from function arguments and returned from function calls onto the stack and unwind the stack at the end of each function. In this case, b will be pushed onto the stack and, therefore, will be a global root.

  • Production quality compilers perform sophisticated register allocation and maintain only live references on the stack, either overwriting or nulling references on the stack as they die. In this case, b is dead during the call to DoSomeLongWork so its stack entry will have been nulled or overwritten.

None of this can be inferred from the source code without details of what exactly the compiler will do. For example, my HLVM project is only a toy at this stage, using the former technique, but it will actually collect a and b in this case because the call to DoSomeLongWork is a tail call.

Or a root is only the first reference to the instance?

The global roots are the references the GC begins with in order to traverse all of the reachable data in the heap. The global roots are typically global variables and threads' stacks but more sophisticated GC algorithms can introduce new kinds of global roots, e.g. remembered sets.

Jon Harrop