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


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.

Comments (41)
  1. Marquess says:

    Then there's the Homer Simpson garbage collector, which spends all it's money at program initialization and then takes the garbage from other processes to make up for it.

  2. nathan_works says:

    The death certificate link has a spurious quote in it.

    But like my comment yesterday about if you're trying to walk the stack in C#, you're doing it wrong..

    If you don't want your object "deleted", don't drop your references to it. There are a zillion ways to prevent this.. If you need to check if it's been deleted, you're doing it wrong.

    [Link fixed, thanks. -Raymond]
  3. SimonRev says:

    Funny thing, but I read this and think, "Wow — yet another example of why many of those who read this blog prefer deterministic resource reclamation."  It is obvious that this customer is thinking in those terms.

  4. Christoph says:

    This may have been a long discussion, but it was highly interesting. To me, at least. As were all posts from CLR week. Please don't try to keep posts shorter than necessary. :)

  5. nathan_works says:

    SimonRev — I will agree, it feels "wrong" to not explicitly say "delete this object" when writing lots of C# code.. That you've got all these "new" operations, with out the corresponding delete.

  6. Ivo says:

    I can think of a scenario where you would want to do what the customer asks for. You may want to verify (in debug builds) that some resource has been freed (unreferenced) properly. Imagine in a game when you go from one level to the next. If any code (like the collision system) still refers to any of the objects in the old level, that's a bug and should be caught and fixed. Otherwise the game might not crash, but may misbehave in subtle ways (like bumping into some invisible wall left over from the last level). Of course I would do that between levels, when calling the GC and paying the price (particularly in debug builds) is acceptable.

    The problem with this test is that it doesn't tell you who has the extra reference, just that there is one (or more). But still, it is a starting point for locating the bug.

    [That's not quite what the customer asked for. You want to know whether the reference count is zero or nonzero. For that, use WeakReference. The customer wants to know whether it is one or greater than one. -Raymond]
  7. Jules says:

    Ivo… sure, but the best way of doing something like that would be checking with an external debugging tool that can find references to objects on the heap (I'm assuming such a tool is available for .net; I've never needed to use one there, but have used one in java). I don't really see much need to automate such a test.

  8. Alexander Grigoriev says:

    Does garbage collector track number of hard page faults per second? Because if it only runs as late as possible, it might have to go through objects already paged out. This is the biggest problem I see with GC – it inflates the process working set.

    [That's why CLR GC is generational. See Maoni's blog which I listed as bonus reading. -Raymond]
  9. JS Bangs says:

    Nathan_works, it only looks "wrong" if you've been brain-damaged by too much C++. Yes, I consider C++ a form of brain damage. This despite the fact that I have to write code in C++ every day.

  10. Evan says:

    @JS Bangs:

    In fact, I'd say only if you're writing too much C, because if you're writing what I'd consider good C++, most of your deletes will be done automatically by auto_ptr/shared_ptr.

  11. djeidot says:

    "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."

    I have some issues with this comment. People will often ask for a general solution in order to solve a particular problem. This is because they assume that there won't be a specific function for their case. For instance, I don't go to a forum and ask for a function that determines if the size of an array is bigger than 123, because there is no way that particular function exists and people will just call me stupid. I will ask for a function that gets me the size of the array.

    In this case, the customer was wrong, there was indeed a specific solution for his specific case. But that is the exception, not the rule.

    [A better question would be to ask for a function to determine if the size of an array is bigger than N for some fixed N. Because it may be easier to determine that an array is "big enough" than to determine exactly how big it is. It's easier to answer "Can this library hold at least 5000 books?" than "Exactly how many books can this library hold?"-Raymond]
  12. blah says:

    "performance benefits of garbage collected systems"

    I nominate this as oxy of the month.

  13. Evan says:

    @blah: "I nominate this as oxy of the month."

    Why's that necessarily an oxymoron? Think carefully before you answer. (Hint: it is sometimes the case that a GC'd system will perform better than manual memory management.)

  14. Paul M. Parks says:

    @blah: Your dismissive comment leads me to believe you might not have read this:

    msdn.microsoft.com/…/ms973838.aspx

    For several usage scenarios, garbage collection does indeed out-perform manually managed memory heaps. Don't dismiss it until you've measured it.

  15. @BarfieldMV:

    There are facilities to handle the situation where your type references large portions of memory the GC might otherwise be unaware of.  See GC.AddMemoryPressure().  I'm pretty sure Bitmap does it, but if your type maintains several Bitmap instances you might have to do it as well.

  16. SimonRev says:

    @Nathan & JS

    Anymore, I tend to consider a manual call to delete in C++ a "code smell."  Good chance there is an error there.  In any case it probably means you didn't use RAII.  The first thing you should do with just about memory resource you get is stick it in shared_ptr, scoped_ptr or unique_ptr. (Note:  shared_ptr at least probably reduces performance to be roughly equal or less than GCed memory, but does preserve deterministic destruction semantics).

  17. Ooh says:

    @blah: Interestingly enough, it's especially in long-running server scenarios where a GC really shines. The reason is that the .NET GC is a compacting GC, i.e. it does not only reclaim memory but "defragments" the memory, which results in better locality of objects. Therefore a compacting GC actively avoids heap fragmentation, which is essential for scalable Web apps.

    That's not to say that you can't write scalable web apps using explicit memory management (as with all the other benefits of managed environments). It's just a lot harder to do.

  18. JD says:

    @mikeb: If an object's finalizer resurrects it, it's common practice to call GC.ReRegisterForFinalize so the finalizer will have another go when the time is right.

  19. bdodson says:

    Any chance you can tell us what the customer was actually trying to do? I'm still trying to come up with a scenario where you'd want to know this (and for which you'd go to the great lengths of asking questions to figure out how to do it rather than just finding some other way to do what you want).

  20. Cory Foy says:

    One of the things I saw on a lot of customer sites was people saying, "But we know X about our system, and so we call GC.Collect".

    The thing is, there are cases when you *do* know more than the Garbage Collector you are using. But unless you've actually run profilers against your code and determined where the bottlenecks are (and the time and memory saved by calling GC.Collect right there) then you may not know as much as you think you do.

    In addition, the GC isn't static in that if there are better ways to do things (or better GCs tailored to your situation) you can use those. If you've decided to preempt that process, then you need to be ensuring that you have tests in place to make sure that it is still the right choice.

    GC.Collect is always guilty until proven innocent.

  21. Anonymous says:

    I am not a managed developer, but judging by your description of weak references, it seemed like naive use of them would be a race with the garbage collector.  The fact that IsAlive was false doesn't mean that in the time between that moment and when you decide to dereference, the GC won't reclaim it.  Looking at the documentation it seems like the dereference will throw InvalidOperationException in this circumstance, so I would almost be inclined to never check IsAlive, and always grab a reference in a try block.

  22. J says:

    @Anonymous: You don't need to worry about exceptions. If you grab the reference from the Target property, you'll get null if it's already been collected. InvalidOp only throws when trying to set an invalid reference.

  23. Marquess says:

    Indeed, with enough memory at hand, a null garbage collector will outperform *any* other memory management strategy :)

  24. BarfieldMV says:

    I just wanted to add a note that if you keep huge objects around (10+ MB of bitmaps) forcing a garbage collect can really speed up your machine. The GC won't see the huge amount of memory space used by those bitmaps and won't force a lvl 3 collec even through it should. (.net only keeps a reference to those bitmaps alive and probably thinks, i'm only using 80 bitmap reference ~ couple Kb of mem so it wont even start a lvl 3 collect)

    Spending the dollar when you're sure the application can miss it is always* a smart move.

    *always for a given value of always that might not be your value of always.

    ps. any comment about a garbage collector not backed up with generation memory snapshots and performance counter information is worthless (ya'll did know you can just read them no?)

  25. mikeb says:

    I haven't done much serious .NEt work in a while, but I still really like .NET week…

    Anyway, prompted by this article, I looked into the difference between the 2 types of WeakReference where it discussed 'resurrection', which sounded interesting. I ended up in the docs for Object.Finalize():

    >> Finalize can take any action, including resurrecting an object (that is, making the object accessible again) after it has been cleaned up during garbage collection. However, the object can only be resurrected once; Finalize cannot be called on resurrected objects during garbage collection. <<

    Now *that* seems like a curious scenario rife with possibilities for screwing things up. I'd be interested in pointers (or references) to where resurrection is a useful technique (maybe Raymond already has something in the .NET Week queue…)

  26. Henning Makholm says:

    @mikeb: It's not so much that resurrection is a Good Idea than the fact that it would be very hard to prevent the programmer from doing it without also disallowing things that do make sense. (Consider, for example the case of cyclic structures of finalizable objects, where one object is finalized first, and another object's finalizer resurrects it afterwards). Given that the language cannot prevent perverse programmers from trying, it should at least do something well-defined and documented when it happens. And the "don't finalize twice" rules is one of the simplest rules that are easy to explain AND reasonably easy to implement without an efficiency penalty.

    That being said, controlled resurrection can sometimes be useful. Imagine, for example, that releasing a resource needs something to happen in a particular thread. The finalizer cannot count on running in any specific thread, so what it'll need to do is to place enter the object on a worklist that the thread walks through from time to time. But as soon as the object is reachable from the worklist, it has technically been resurrected.

  27. Smart Aleck says:

    [A better question would be to ask for a function to determine if the size of an array is bigger than N for some fixed N. Because it may be easier to determine that an array is "big enough" than to determine exactly how big it is. It's easier to answer "Can this library hold at least 5000 books?" than "Exactly how many books can this library hold?"-Raymond]

    Use IsBadReadPtr to determine if the library book pointed-to is a Bad Read.

  28. Ian Boyd says:

    Is there a way to find out if an object has any "strong references" to it?

  29. TwelveBaud says:

    @SmartAleck "Use IsBadReadPtr to determine if the library book pointed-to is a Bad Read."

    Ha! But the problem with that is what's to stop you from wandering out the library's back door, down the road a few blocks, and into my bedroom picking up my mystery novel. Sure, it's not a Bad Read, but what the heck are you doing in my bedroom? ;)

    @IanBoyd "Is there a way to find out if an object has any "strong references" to it?"

    Sure. The answer is yes: the one you're using to ask the question. Of course, now that you've finished asking, the answer might have changed…

  30. ficedula says:

    @Anonymous: The documentation suggests to me that Target returns null if the object has been garbage collected so no Try/Catch is required – InvalidOperation is only thrown when *setting* it – … but otherwise, yes, I'd say that you should just try and retrieve the object, not check IsAlive first. I guess IsAlive is basically just a convenience method which makes it slightly easier to do something like MyCache.RemoveAll(item => !item.IsAlive)

  31. ficedula says:

    @bdodson: Well, I can construct a situation where you might want to know … say you have a cache of some expensive-to-construct items, using WeakReferences (so far so good), and each item takes up a fair amount of memory and requires disposing (to free a native resource). You *might* want to remove items from the cache for various reasons; perhaps they expire after a certain period of time. However because each cached item (ideally) requires disposing, the removal procedure could be:

    -Check the WeakReference is still alive (if not, it's been GC'd and you can't do anything with it anyway!)

    -If it's still alive, get a reference to the item into a local variable, remove the cache entry, and Dispose it to free up the native resource. But you can only Dispose it if no other thread currently has an outstanding reference to the object; Disposing something while it's in use elsewhere would be bad. So you want a way to ask the GC "is there only one outstanding reference to this object, i.e. just my local variable".

    Of course, this is just a guess, but it *is* possible the customer had a "legitimate" reason for asking. Which isn't to say that another solution wouldn't have been better.

    As it happens, this sort of problem – WeakReferences to objects that need to clean up non-managed resources – is a tricky situation to deal with. It's one situation in which a finalizer is actually a really good idea as part of your normal operation.

  32. Anon says:

    @Nathan_works : agreed. I would imagine if we could get the full explanation of the issue, we'd find something more easily resolved farther up the chain than needing to find a reference count for a particular object.

  33. mikeb says:

    @Anonymous: >> judging by your description of weak references, it seemed like naive use of them would be a race with the garbage collector. <<

    Raymond's discussion of the IsAlive property discusses exactly this incorrect, naive use of WeakReference.  If you want to use WeakReference, in almost all cases you should *not* deal with the IsAlive property at all.  Just use the Target property to get the strong reference back (this operation is synchronized with the GC).  If you get a non-null reference, you're good to go.  If you get null back, the object is gone, and you'll need to recreate it (or whatever the appropriate handling for this situation is).

  34. Jeffrey Bosboom says:

    @Henning Makholm: Java has "reference queues" a Reference object can be registered with: blocking queues that a Reference object is placed on when it becomes eligible for collection but before it's actually collected (and it's guaranteed not to be collected while it's in the queue).  What you describe is pretty much what they're for — any time your finalizer does something more than a last-ditch attempt to free a native resource, you should use a reference queue.  Does .NET have anything analogous?

  35. Anonymous Coward says:

    Did you ever find out what the customer actually wanted?

  36. Vulcan says:

    I read an old (old new?) post of yours ending with: "We'll look at that next time." Here is the URL: blogs.msdn.com/…/443384.aspx

    I have not yet been able to find the "next" article/post. Please help. I am trying to determine the very first message a window receives in each of the following cases: top-level windows, child windows and any others.

  37. Ian Boyd says:

    @TwelveBaud No; i have a "weak reference" (i.e. WeakReference). This is implicit in the question, otherwise one might short-circuit the question and skip to a non-answer.

    The question can be re-formulated, to avoid your non-answer: "Is there a way to find out if an object has any "strong references" to it, besides the [strong] one i'm using to ask the question?"

  38. laonianren says:

    Ian Boyd wrote "Is there a way to find out if an object has any "strong references" to it, besides the [strong] one i'm using to ask the question?"

    In other words, does the object have more than one reference?  Which is exactly the question that Raymond's post answers.

    If you start with a weak reference the question is partially decidable.  If the weak reference is dead there were no strong references; if it's alive that tells you nothing about strong references.

  39. bdodson says:

    @ficedula – thanks for the example you gave. I can now see why a customer might look for this capability. Of course, the way you usually have to end up implementing such a cache/object pool is to have clients of the cache "check out" (i.e. add a reference) to an object and then check it back in again with the cache later. That way the cache keeps it's own reference count so it knows if it's free to Dispose something.

    I've seen this done with pools of expensive objects, although usually checking the object out removes it from the pool so you don't have to worry about the number of references (it's always effectively 1 or 0).

  40. Matt says:

    I think that the customer must have had a background in Objective-C programming (Mac/iPhone).

    They have this concept of a reference count on every object. You increase the count by calling [object retain] and decrease it with [object release]. There is also a method, whose name I forgot, to get the reference count of an object. How it works is pretty simple: when you call [object release] and the reference count hits zero, the object is immediately deallocated.

Comments are closed.

Skip to main content