The GC may collect whichever object for which it can prove that it makes no difference for the application. One way to think about it is the following: objects are allocated forever but the implementation is allowed to reclaim memory as long as it makes sure that you cannot notice it.
In your example, at the first GC.Collect()
, the first object reference has been written to r1
, and r1
is a static variable, thus somehow "permanent". From the point of view of the GC, just about any code could use that static variable, so the GC cannot make sure that the first object will not be used. Therefore it cannot collect it. The second object, on the other hand, is written in a local variable. The local variable is limited to the method code (it disappear when the method exits) and the GC is able to notice that r2
will not be used anymore (the GC only needs to look at the method, not the whole code, so this is part of what the GC can reasonably do). Therefore, the GC may collect the second object (there is no obligation here).
The third object will still be used after the first GC.Collect()
: its ToString()
method is called. Hence the GC should not collect it. However, it is conceivable that the VM notices that this is a true Object
instance (not any other class), inlines the call to ToString()
, and further concludes that the whole ToString()
call is an elaborate way of doing nothing (no visible outcome). As such, the GC might be able to collect the third object and skip the call to ToString()
altogether.
Of course the GC is free not to collect things as well. The whole point of the GC is that it runs "behind the scene", without impacting the code execution (this is not entirely true in presence of finalization, but that's an extra layer of complexity which should not be envisioned until you master the basics).
Wilson's article is indeed a must-read for anybody wishing to understand what garbage collection does.