Does Garbage Collection Run During Debug

Does garbage collection run during debug?

Garbage collection is optimized differently when running not in the debugger, yes. In particular, the CLR can detect that a variable won't be used for the rest of a method, and treat it as not a GC root any more. In the debugger, variables in scope act as GC roots throughout the method so that you can still examine the values with the debugger.

However, that should rarely be a problem - it should only affect things if a finalizer actually performs some clean-up, and if you're explicitly tidying things up in a timely way (e.g. with using statements) you usually wouldn't notice the difference.

Why C# Garbage Collection behavior differs for Release and Debug executables?

The short answer is that the GC isn't required to do anything like what you're describing. The long answer is that it's not uncommon for something to work more pessimistically under debug configuration, in order to allow you to debug more easily.

For example, in this case, because you declared obj as a local variable somewhere inside the method, the C# compiler can reasonably choose to retain references of that instance, so that utilities like the Locals window or the Watch windows in Visual Studio can function predictably.

Indeed, this is the IL of your code generated using the Debug configuration:

.method private hidebysig static void Main (
string[] args
) cil managed
{
.entrypoint
.locals init (
[0] class [mscorlib]System.WeakReference weakRef,
[1] class _GC.Program/TestClass obj
)

IL_0000: nop
IL_0001: nop
IL_0002: newobj instance void _GC.Program/TestClass::.ctor()
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: newobj instance void [mscorlib]System.WeakReference::.ctor(object)
IL_000e: stloc.0
IL_000f: ldstr "Leaving the block"
IL_0014: call void [mscorlib]System.Console::WriteLine(string)
IL_0019: nop
IL_001a: nop
IL_001b: ldstr "GC.Collect()"
IL_0020: call void [mscorlib]System.Console::WriteLine(string)
IL_0025: nop
IL_0026: call void [mscorlib]System.GC::Collect()
IL_002b: nop
IL_002c: ldc.i4 1000
IL_0031: call void [mscorlib]System.Threading.Thread::Sleep(int32)
IL_0036: nop
IL_0037: ldstr "weakRef.IsAlive == {0}"
IL_003c: ldloc.0
IL_003d: callvirt instance bool [mscorlib]System.WeakReference::get_IsAlive()
IL_0042: box [mscorlib]System.Boolean
IL_0047: call void [mscorlib]System.Console::WriteLine(string, object)
IL_004c: nop
IL_004d: ldstr "Leaving the program"
IL_0052: call void [mscorlib]System.Console::WriteLine(string)
IL_0057: nop
IL_0058: ret
}

And this is the IL generated using the Release configuration:

.method private hidebysig static void Main (
string[] args
) cil managed
{
.entrypoint
.locals init (
[0] class [mscorlib]System.WeakReference weakRef
)

IL_0000: newobj instance void _GC.Program/TestClass::.ctor()
IL_0005: newobj instance void [mscorlib]System.WeakReference::.ctor(object)
IL_000a: stloc.0
IL_000b: ldstr "Leaving the block"
IL_0010: call void [mscorlib]System.Console::WriteLine(string)
IL_0015: ldstr "GC.Collect()"
IL_001a: call void [mscorlib]System.Console::WriteLine(string)
IL_001f: call void [mscorlib]System.GC::Collect()
IL_0024: ldc.i4 1000
IL_0029: call void [mscorlib]System.Threading.Thread::Sleep(int32)
IL_002e: ldstr "weakRef.IsAlive == {0}"
IL_0033: ldloc.0
IL_0034: callvirt instance bool [mscorlib]System.WeakReference::get_IsAlive()
IL_0039: box [mscorlib]System.Boolean
IL_003e: call void [mscorlib]System.Console::WriteLine(string, object)
IL_0043: ldstr "Leaving the program"
IL_0048: call void [mscorlib]System.Console::WriteLine(string)
IL_004d: ret
}

Notice how in the Debug build, the TestClass instance is retained as a local throughout the entire method:

    .entrypoint
.locals init (
[0] class [mscorlib]System.WeakReference weakRef,
[1] class _GC.Program/TestClass obj
)

The fact that you declared that variable in a nested scope in the C# code is irrelevant, because the IL code doesn't have an equivalent notion of nested scopes. So, the variable is declared as a local of the entire method either way.

Also notice how if you manually perform this change in your C# code (local variable inlining):

        WeakReference weakRef;
{
weakRef = new WeakReference(new TestClass());
Console.WriteLine("Leaving the block");
}

Then the IL of the Debug build skips the local declaration as well, matching the Release configuration:

.method private hidebysig static void Main (
string[] args
) cil managed
{
.entrypoint
.locals init (
[0] class [mscorlib]System.WeakReference weakRef
)

And similarly, the Debug configuration output matches the output of the Release configuration as well:

Leaving the block
GC.Collect()
~TestClass()
weakRef.IsAlive == False
Leaving the program

Obviously, the reason for this is that part of the optimizations that the C# compiler performs when building using the Release configuration is to automatically inline local variables wherever possible. And that's where the different behavior kicks in.

Call garbage collect in visual studio

You can write GC.Collect() in the Immediate Window.

GC.COllect() doesnt seem to work in debug mode

In Debug mode, the compiler does not optimize the local variables. Therefore, the reference to A still exists. In Release mode, the compiler optimized the usage so that the reference is thrown away and the object can be collected.

When does the .net garbage collector run?

You have to use .NET 4.0, what you are asking for isn't supported in eariler versions.

Essentially you call the WaitForFullGCApproach and the WaitForFullGCComplete methods in a loop. WaitForFullGCApproach will block until you see a GC, WaitForFullGCComplete will block until the GC is complete.

Please read this article carefully. If you use this method then you are responsible for makign sure the garbage collection actually occurs. If you mess this up you could break the GC and quickly run out of memory.

http://msdn.microsoft.com/en-us/library/cc713687.aspx

Why isn't the last object destroyed by the garbage collector?

It seems the reason is related to the garbage collector, which behaves different in debug mode than in release mode. As some have commented, the garbage compiler might still hold a reference to Dummy 5 when the DummyTest() method closes, as it would keep the value for debugging purposes. In release mode, the garbage collector behaves more aggressively.

This question has similarities with Why C# Garbage Collection behavior differs for Release and Debug executables? and answered in Does garbage collection run during debug? but this question is still interesting as it applies to a situation where a resource is part of a more complex infinite enumeration.

This is not a memory leak, though! It's just that when the project is compiled in debug mode, the garbage collector tends to hang on a bit longer to some resources until after the method is closed, as any running debugger might still be evaluating the value. (It doesn't know that there's no debugger running.)

How to debug .net Garbage Collection?

I've found the best way to do this is to use windbg and the SOS (son of strike) extension. It has a rather cryptic command line but it is very powerful. It has the capability to dump the heap and divide it by the GC generational heap. Once you get past the initial learning curve, it's very easy to track what objects are alive in what portion of the heap. Here are a few web sites with examples of using SOS

  • http://blogs.msdn.com/tess/archive/2005/11/25/dumpheap-stat-explained-debugging-net-leaks.aspx
  • http://geekswithblogs.net/.NETonMyMind/archive/2006/03/14/72262.aspx
  • http://dotnetdebug.net/2005/07/04/dumpheap-parameters-and-some-general-information-on-sos-help-system/

EDIT OP asked about the location of sos.dll. It is included with the install of the .Net Framework. It is located at

%WINDIR%\Microsoft.Net\Framework\V2.0.50727\sos.dll

But once you have windbg loaded you don't need the full path. Just us the .loadby method.

.loadby sos mscorwks.dll

It will look for the version of sos in the same directory as the current version of mscorwks (the CLR)



Related Topics



Leave a reply



Submit