Differences between garbage collectors, calls and callvirt and debug version

I have a class:

public class SomeClass { public int I; public SomeClass(int input) { I = input; Console.WriteLine("I = {0}", I); } ~SomeClass() { Console.WriteLine("deleted"); } public void Foo() { Thread.Sleep(1000); Console.WriteLine("Foo"); } } 

and this program:

 class Program { static void Main(string[] args) { new Thread(() => { Thread.Sleep(100); GC.Collect(); }) { IsBackground = true }.Start(); new SomeClass(10).Foo(); // The same as upper code // var t = new SomeClass(10); // t.Foo(); } } 

When I run this code in debug mode , I have the following result:

 I = 10 Foo deleted 

but when I change the mode to Release , the result will change to:

 I = 10 deleted Foo 

As I understand it, the difference is with call and callvirt : when optimization starts in Release mode, the compiler looks at the Foo method and cannot find a link to SomeClass in this method and why this method is called as a static method at the address, and the garbage collector can collect this object.

Otherwise, if I change the Foo method (for example, add Console.WriteLine(I) to this method), the compiler will not decide to call this method as call , and it must be called by the pointer to the instance using callvirt and the garbage collector will not collect this object.

Can you explain more clearly what is happening here (why the GC can assemble the object, and if so, how the method works).

+2
source share
2 answers

I doubt this really relates to call and callvirt .

I strongly suspect this is simply because you are not using any fields inside SomeClass.Foo . The garbage collector can collect an object if it is sure that none of the data will be re-specified - so no code will look at any references to the object or any fields in the object.

Basically, if you are writing a finalizer, and you need to make sure that the object is not finalized, and the methods inside this object are running, you should be very careful. You can use GC.KeepAlive(this) at the end of the methods as one approach, but it's a little ugly. I personally would really try to avoid the need for finalization at all, if possible. I don’t remember the last time I wrote it. (For more, see Joe Duffy's blog .)

When a debugger is connected, the GC is much less aggressive about what it can collect - after all, if you can break into the debugger at any time and check the fields of any object that is an object of the running instance method, which eliminates the possibility of garbage collection .

+5
source

When you debug, the whole system 1 extends the lifespan of objects beyond their natural lifetime.

So, when the thread executes this method:

 public void Foo() { Thread.Sleep(1000); Console.WriteLine("Foo"); } 

This method does not use this . Therefore, when not debugging, after this method has started, it no longer requires the object to exist.

However, when debugging, you could set a breakpoint for this Console.WrtieLine method, and from there decide to check this . Thus, the system tries to keep this alive (as well as any local variables that are no longer used in the body of the method).


1 This old presentation shows how JIT and GC actually work together to determine which links are live (see slide 30 onwards). I understand that JIT works more to help with debugging - not reusing the local variable slots that it can (so that the values ​​are still visible), and informing the GC that all variables are alive in the whole method, not more which can provide.

+1
source

Source: https://habr.com/ru/post/1246859/


All Articles