There's the concept of a "root" or "strong reference". These are things like static
variables, or any objects currently in use. If an object on the heap is reference
by a "root" (either directly or indirectly), it will not be garbage collected. It
is only when the object has no references that the GC will decide that it can collect
it.
When the GC runs, it starts at the roots, and works it's way "down" through all
of the objects, following the references. It marks each of them as "in use". When
that's done, everything it didn't mark is considered "unused", and is finalized
(the memory is de-allocated).
Objects referenced by static variables will only be garbage collected when the relevant
AppDomain is garbage collected. In client applications, there's often just a single
AppDomain which lives for the duration of the process. (An exception is when the
application uses a plug-in architecture - different plug-ins may be loaded in different
AppDomains and the AppDomain may be unloaded later.)
In ASP.NET, "AppDomain recycling" happens periodically (for various reasons) - when
this occurs, and the static variables within that AppDomain will no longer act as
GC roots, and thus won't prevent objects being garbage collected.