The garbage collector exists to give the illusion of infinite memory:
You keep allocating and allocating, and the GC will automatically free memory that it knows will never be used again.
However, in your example, all those objects will be used again - they're needed on the next
TimerCall()
.
A timer event handler cannot be garbage collected because it is still used on the next tick of the timer!
You can think of a global list that contains all active timers - a timer will never be garbage collected while it is active. If it worked any other way, the semantics of your program (how many timer calls you get) would depend on when exactly the GC runs - which is obviously undesirable.
To solve the issue, you have to stop the timer (
timer.Dispose()
) - this will remove it from the list of active timers, and will allow the GC to collect the timer, the event handler, and related objects.
EDIT: actually, the above answer applies only to
System.Timers.Timer
and
System.Windows.Forms.Timer
.
System.Threading.Timer
has the strange property that it does not keep itself alive, but only keeps the delegate alive. If there's a reference from the delegate back to the timer, this has the same effect as keeping the timer itself alive. Otherwise, the timer will stop
sometime after the timer object became unreachable (so you still can get the undesirable behavior).
In your example, there is the reference chain:
static list of timer callbacks => _TimerCallback => TimerCallback => MyTimerData => Timer
The timer would stop itself in its finalizer, but it can't be garbage collected because it still is reachable, from the static entry that it created itself.
This is a classical leak situation in the .NET world: a static reference is not nulled out by the finalizer, because the finalizer doesn't run, because the static reference isn't nulled out yet. The GC can't look into the future, so doesn't know that the finalizer would remove the last reference to the object.
That said, if Microsoft wanted to fix this leak (which they won't, since it could break programs that depend on such 'unreachable' timers to perform their periodic work), they could now do it, as .NET 4.0 introduced a new type of GC handle that allows a program to communicate this scenerio to the GC:
System.Runtime.CompilerServices.DependentHandle
(unfortunately it's
internal
; publically usable only via the
ConditionalWeakTable[
^] class). This is an implementation of
ephemerons[
^] in the .NET GC.