How do I get the reference count of a CLR object?

Raymond Chen

A customer asked the rather enigmatic question (with no context):

Is there a way to get the reference count of an object in .Net?

Thanks,
Bob Smith
Senior Developer
Contoso

The CLR does not maintain reference counts, so there is no reference count to “get”. The garbage collector only cares about whether an object has zero references or at least one reference. It doesn’t care if there is one, two, twelve, or five hundred—from the point of view of the garbage collector, one is as good as five hundred.

The customer replied,

I am aware of that, yet the mechanism is somehow implemented by the GC…

What I want to know is whether at a certain point there is more then one variable pointing to the same object.

As already noted, the GC does not implement the “count the number of references to this object” algorithm. It only implements the “Is it definitely safe to reclaim the memory for his object?” algorithm. A null garbage collector always answers “No.” A tracing collector looks for references, but it only cares whether it found one, not how many it found.

The discussion of “variables pointing to the same objects” is somewhat confused, because you can have references to an object from things other than variables. Parameters to a method contain references, the implicit this is also a reference, and partially-evaluated expressions also contain references. (During execution of the line string s = o.ToString();, at the point immediately after o.ToString() returns and before the result is assigned to s, the string has an active reference but it isn’t stored in any variable.) And as we saw earlier, merely storing a reference in a variable doesn’t prevent the object from being collected.

It’s clear that this person solved half of his problem, and just needs help with the other half, the half that doesn’t make any sense. (I like how he immediately weakened his request from “I want the exact reference count” to “I want to know if it is greater than one.” Because as we all know, the best way to solve a problem is to reduce it to an even harder problem.)

Another person used some psychic powers to figure out what the real problem is:

If I am reading properly into what you mean, you may want to check out the Weak­Reference class. This lets you determine whether an object has been collected. Note that you don’t get access to a reference count; it’s a zero/nonzero thing. If the Weak­Reference is empty, it means the object has been collected. You don’t get a chance to act upon it (as you would if you were the last one holding a reference to it).

The customer explained that he tried Weak­Reference, but it didn’t work. (By withholding this information, the customer made the mistake of not saying what he already tried and why it didn’t work.)

Well this is exactly the problem: I instantiate an object and then create a Weak­Reference to it (global variable).

Then at some point the object is released (set to null, disposed, erased from the face of the earth, you name it) yet if I check the Is­Alive property it still returns true.

Only if I explicitly call to GC.Collect(0) or greater before the check it is disposed.

The customer still hasn’t let go of the concept of reference counting, since he says that the object is “released”. In a garbage-collected system, object are not released; rather, you simply stop referencing them. And disposing of an object still maintains a reference; disposing just invokes the IDisposable.Dispose method.

FileStream fs = new FileStream(fileName);
using (fs) {
 ...
}

At the end of this code fragment, the File­Stream has been disposed, but there is still a reference to it in the fs variable. Mind you, that reference isn’t very useful, since there isn’t much you can do with a disposed object, Even if you rewrite the fragment as

using (FileStream fs = new FileStream(fileName)) {
 ...
}

the variable fs still exists after the close-brace; it simply has gone out of scope (i.e., you can’t access it any more). Scope is not the same as lifetime. Of course, the optimizer can step in and make the object eligible for collection once the value becomes inaccessible, but there is no requirement that this optimization be done.

The fact that the Is­Alive property says true even after all known references have been destroyed is also no surprise. The environment does not check whether an object’s last reference has been made inaccessible every time a reference changes. One of the major performance benefits of garbage collected systems comes from the de-amortization of object lifetime determination. Instead of maintaining lifetime information about an object continuously (spending a penny each time a reference is created or destroyed), it saves up those pennies and splurges on a few dollars every so often. The calculated risk (which usually pays off) is that the rate of penny-saving makes up for the occasional splurges.

It does mean that between the splurges, the garbage collector does not know whether an object has outstanding references or not. It doesn’t find out until it does a collection.

The null garbage collector takes this approach to an extreme by simply hoarding pennies and never spending them. It saves a lot of money but consumes a lot of memory. The other extreme (common in unmanaged environments) is to spend the pennies as soon as possible. It spends a lot of money but reduces memory usage to the absolute minimum. The designers of a garbage collector work to find the right balance between these two extremes, saving money overall while still keeping memory usage at a reasonable level.

The customer appears to have misinterpreted what the Is­Alive property means. The property doesn’t say whether there are any references to the object. It says whether the object has been garbage collected. Since the garbage collector can run at any time, there is nothing meaningful you can conclude if Is­Alive returns true, since it can transition from alive to dead while you’re talking about it. On the other hand, once it’s dead, it stays dead; it is valid to take action when Is­Alive is false. (Note that there are two types of Weak­Reference; the difference is when they issue the death certificate.)

The name Is­Alive for the property could be viewed as misleading if you just look at the property name without reading the accompanying documentation. Perhaps a more accurate (but much clumsier) name would have been Has­Not­Been­Collected. The theory is, presumably, that if you’re using an advanced class like Weak­Reference, which works “at the GC level”, you need to understand the GC.

The behavior the customer is seeing is correct. The odds that the garbage collector has run between annihilating the last live reference and checking the Is­Alive property is pretty low, so when you ask whether the object has been collected, the answer will be No. Of course, forcing a collection will cause the garbage collector to run, and that’s what does the collection and sets Is­Alive to false. Mind you, forcing the collection to take place messes up the careful penny-pinching the garbage collector has been performing. You forced it to pay for a collection before it had finished saving up for it, putting the garbage collector in debt. (Is there a garbage collector debt collector?) And the effect of a garbage collector going into debt is that your program runs slower than it would have if you had let the collector spend its money on its own terms.

Note also that forcing a generation-zero collection does not guarantee that the object in question will be collected: It may have been promoted into a higher generation. (Generational garbage collection takes advantage of typical real-world object lifetime profiles by spending only fifty cents on a partial collection rather than a whole dollar on a full collection. As a rough guide, the cost of a collection is proportional to the number of live object scanned, so the most efficient collections are those which find mostly dead objects.) Forcing an early generation-zero collection messes up the careful balance between cheap-but-partial collections and expensive-and-thorough collections, causing objects to get promoted into higher generations before they really deserve it.

Okay, that was a long discussion of a short email thread. Maybe tomorrow I’ll do a better job of keeping things short.

Bonus chatter: In addition to the Weak­Reference class, there is also the GC­Handle structure.

Bonus reading: Maoni’s WebLog goes into lots of detail on the internals of the CLR garbage collector. Doug Stewart created this handy index.

0 comments

Discussion is closed.

Feedback usabilla icon