Question about the garbage collector

D

Daniel Lidstrom

Hello,

I am trying to understand how the garbage collector works. To do this I
created a simple test with a timer. I create a timer and then release all
references leading to this timer, then I force a garbage collection. To my
surprise the timer is still sparking off events! Here's the code:


using System.Timers;
using System;

public class TimerTest
{
public TimerTest()
{
var timer = new Timer(1000);
timer.Elapsed += (o, e) => { Console.WriteLine("Timer elapsed"); };
timer.Start();
}

public static void Main()
{
{
var timerTest = new TimerTest();
}

Console.ReadLine();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect(); // necessary? does not seem to help
Console.ReadLine();
}
}


Can someone please explain why the Elapsed event is still firing after the
first ReadLine()? Thanks in advance!

Regards,

Daniel Lidström
 
M

Mr. Arnold

Daniel said:
Hello,

I am trying to understand how the garbage collector works. To do this I
created a simple test with a timer. I create a timer and then release
all references leading to this timer, then I force a garbage collection.
To my surprise the timer is still sparking off events! Here's the code:

I guess you can just set the Timer to null Timer=null that should kill it.


Here is a link that I got from a developer on the job as we talked about
how a Web server can still keep objects in memory, such as a ADO.NET
Entity Framework compiled query object, and it's there the next time the
stateless Web application wants to use that object again, and not having
to recompile the query object again, because it's always in (static)
memory due to the W3WP.exe is always executing.

<http://www.simple-talk.com/dotnet/.net-framework/understanding-garbage-collection-in-.net/>
 
D

Daniel Lidstrom

Mr. Arnold said:
I guess you can just set the Timer to null Timer=null that should kill it.

Neither setting timer nor timerTest to null helped in this case. Even if it
had helped I think that in a larger
application it becomes impractical to have to set unused references to null.
Here is a link that I got from a developer on the job as we talked about
how a Web server can still keep objects in memory, such as a ADO.NET
Entity Framework compiled query object, and it's there the next time the
stateless Web application wants to use that object again, and not having
to recompile the query object again, because it's always in (static)
memory due to the W3WP.exe is always executing.

<http://www.simple-talk.com/dotnet/.net-framework/understanding-garbage-collection-in-.net/>

Thanks for the reference!

Daniel
 
A

Andrew Baker

Because the timer is active (enabled), there's still a live reference to
it, preventing the garbage collection. You've not got direct access to
this reference, 'the system' has got one inside the workings of the .net
framework somewhere.
 
M

Mr. Arnold

Daniel said:
Neither setting timer nor timerTest to null helped in this case. Even if
it had helped I think that in a larger
application it becomes impractical to have to set unused references to
null.

I guess you have never worked in the COM, DCOM or COM+ environment, back
in the day for large applications, where objects had to be set manually
to null to release resources. These technologies are still being used to
this day.
 
D

Daniel Lidstrom

Andrew Baker said:
Because the timer is active (enabled), there's still a live reference to
it, preventing the garbage collection. You've not got direct access to
this reference, 'the system' has got one inside the workings of the .net
framework somewhere.

Ah yes, that would explain it. Thanks for the insight Andrew.

Daniel
 
A

Adam Clauss

Because the timer is active (enabled), there's still a live reference
to it, preventing the garbage collection. You've not got direct access
to this reference, 'the system' has got one inside the workings of the
.net framework somewhere.

I'm not sure that's accurate - I'm pretty sure I've seen cases where I
created and enabled a timer, forget to stash away a reference to it, and
the timer would fire once or twice - and then nothing. I assumed it had
gotten garbage collected.

-Adam
 
D

David Boucherie & Co

This is a bit simplified, but in essence, the garbage collector works as
follows...


First off, the garbage collector runs whenever:

· Physical memory is low.
· The memory that is used by allocated objects (on the managed heap)
surpasses a certain threshold (which is dynamically calculated while
running).
· GC.Collect() is called (the method you use in your code).


Allocated objects (in the managed heap) get a generation:

· 0: very-short lived objects (like temporary variables).
· 1: short-lived objects
· 2: long-lived objects

Garbage collection is run on a specific generation depending on certain
conditions. All objects of that generation or lower are then collected.
(A generation 2 garbage collection is also known as a full garbage
collection.)

Objects that "survive" a collection pass are promoted to the next
generation. So when a generation 0 garbage collection occurs, any object
not collected becomes generation 1, 1 becomes 2, and 2 remains 2.


A full garbage collection has three phases:

· Live objects are marked, dead objects become "condemned".
· References to objects that will be compacted (moved together, simply
put) are updated.
· Space occupied by dead objects is reclaimed and surviving objects are
compacted. However, dead objects that have finalizers are not reclaimed,
but marked as finalize pending. Also, all references in the finalizers
cause the objects referenced to be kept alive too, even if they would
otherwise be condemned!

A finalization thread is started after collection is complete and starts
executing all pending finalizers, causing the finalized objects to
become dead again. However, they are not collected until the garbage
collector makes its next run.



On top of that, full garbage collection is expensive in time and CPU
power, so usually garbage collection is only partial and works only on
lower generation objects.



In short: when you run your garbage collector, there is no way to be
sure that a certain object will be effectively destroyed at any one
time, nor can you exactly predict when finalizers will be run.

To force a full garbage collection (generation 2), use:
GC.Collect(2, GCCollectionMode.Forced );



Now that I have explained all that... I have NO CLUE why your timer
doesn't get killed. :p It doesn't even finalize after disposing it, even
when forcing a full garbage collection! The finalizer only runs when the
program ends (after ReadLine()).
I suppose some hidden references exist... but I don't see where. I even
tried with a simple class, replacing your timer with an int instead, and
still no joy.

Anyone can explain it? Because I can't!



On a side note:
Your code contains a resource leak.
Your Timer will eventually be collected, but as you don't Dispose() it,
it will keep its system resources located.


I changed your code a bit, and to my astonishment, the Timer isn't
collected...


using System;
using System.Timers;

namespace ConsoleApplication1
{
public class TimerTest : IDisposable
{
Timer timer;

public TimerTest()
{
timer = new Timer( 1000 );
timer.Elapsed += ( o, e ) => { Console.WriteLine( "Timer elapsed
" + e.SignalTime.ToLocalTime() ); };
timer.Start();
}

~TimerTest()
{
Console.WriteLine( "Finalizing!" );
}

public static void Main()
{
{
TimerTest timerTest = new TimerTest();
timerTest.Dispose();
}

Console.WriteLine( "Collection 1." );
GC.Collect( 2, GCCollectionMode.Forced );
Console.WriteLine( "Suspending thread waiting for finalizers." );
GC.WaitForPendingFinalizers();
Console.WriteLine( "Collection 2." );
GC.Collect( 2, GCCollectionMode.Forced );
Console.ReadLine();
}

#region IDisposable Members

public void Dispose()
{
Console.WriteLine( "Disposing timer." );
timer.Dispose();
}

#endregion
}
}
 
D

David Boucherie & Co

Andrew said:
Because the timer is active (enabled), there's still a live reference to
it, preventing the garbage collection. You've not got direct access to
this reference, 'the system' has got one inside the workings of the .net
framework somewhere.



That doesn't seem to be it.

I tried Daniel's code with an int instead of a Timer, and still no joy.
I can't explain it...

See my answer before, about the workings of the garbage collector... I
thought I understood the thing pretty well. It seems I was delusional... :p



using System;

namespace ConsoleApplication1
{
public class SomeClass : IDisposable
{
int someHandle;

public SomeClass()
{
someHandle = 1;
}

~SomeClass()
{
Console.WriteLine( "Finalizing!" );
}

public static void Main()
{
{
SomeClass someObject = new SomeClass();
someObject.Dispose();
}

Console.WriteLine( "Collection 1." );
GC.Collect( 2, GCCollectionMode.Forced );
Console.WriteLine( "Suspending thread waiting for finalizers." );
GC.WaitForPendingFinalizers();
Console.WriteLine( "Collection 2." );
GC.Collect( 2, GCCollectionMode.Forced );
Console.ReadLine();
}

#region IDisposable Members

public void Dispose()
{
Console.WriteLine( "Disposing the handle." );
someHandle = 0;
}

#endregion
}
}
 
D

David Boucherie & Co

Addendum:

It may be a good idea when implementing Dispose() to finalize your
object yourself and tell the garbage collector that there is no need for
it to finalize your object.

I didn't do that in my previous code, because I wanted to see when the
finalization fired.

In the code I sent before, write your finalization code in your
Dispose() method and leave the ~TimerTest() out.

public void Dispose()
{
Console.WriteLine( "Disposing timer." );
timer.Dispose();
GC.SuppressFinalize( this );
}

Since you have to Dispose() anyway, you can as well finalize yourself.
This gives as added bonus that you have control over when "finalization"
takes place.
 
D

David Boucherie & Co

Hey Peter...

Peter said:
That last statement is either incorrect, or you are being sloppy with
your terminology.

Or both! Ha! =P

Thanks for fixing my post. Of course, I feel really stupid now, but at
least I learned something. :)

David
 
J

Jon

"It's entirely about whether the object is reachable or not."

There's a difference between what is actually not reachable, and what the runtime thinks is not
reachable.
 
P

Peter Kane

Not as far as GC is concerned. The only "reachable" that matters for the
purposes of discussing GC is what the runtime thinks is reachable.
Peter, apols for the direct email, I've only just realised MS has
started discontinuing newsgroup access via nntp - did you set up a nntp
bridge or do you post via http ? Just curious

Many thanks
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top