Weak references have no effect on object lifetime


The Weak­Reference class lets you retain a reference to an object while still permitting the object to be garbage collected. When that happens, then the Is­Alive property is false and the Target property is null. (Related discussion.)

Note, however, that weak references do not alter the decision of the garbage collector whether or not an object is garbage. It merely lets you observe the garbage collector’s decision.

Some people think that Weak­Reference means “Treat this as a regular (strong) reference most of the time, but if there is memory pressure, then you can reclaim the object.” This type of reference is called a Soft­Reference in Java, but the CLR has no analogous concept as far as I’m aware. In the CLR, weak references do not extend the lifetime of an object.

It’s sort of like the Prime Directive from Star Trek: Weak references follow a policy of non-interference with the natural course of the GC.

Comments (18)
  1. Joshua says:

    Incidentally, WeakReference is implemented in terms of GCHandle, so if your collection of WeakReference is behaving badly, consider replacing (but don't forget to clean up in Finally).

  2. Vilx- says:

    Since (under normal circumstances) GC is only run when there is memory pressure, how is "Treat this as a regular (strong) reference most of the time, but if there is memory pressure, then you can reclaim the object." incorrect?

  3. Dan Bugglin says:

    You answered your own question: GC can be run in other circumstances under non-normal circumstances.

    IIRC you can manually invoke it, for one.  You probably shouldn't ever need to, but The Daily WTF has taught me to expect such things.

  4. Anon says:

    @The MAZZTer

    YOU don't need to, but the guy across the hall "needs to" all the time.

    Or, to put it another way, "This is why we can't have nice things."

  5. Ursus says:

    Unlike the Prime Directive in Star Trek, the policy is actually followed.

  6. @Vilx- Like MAZZTer said, the GC can run at any point, not just memory pressure.

    As much as we like to say "never manually garbage collect", it is sometimes necessary. One program I wrote did Excel import through COM. Every time the user opened a file, it opened excel.exe. When done, we would release all resources in the C# code, but a zombie excel.exe remained alive. Forcing a GC would ACTUALLY release the handles, and Excel would quit.

    So we were left with the choice of let hundreds of excel.exe processes accumulate over the course of a day, or force a GC after every import. In the end, we got less complaints of low system memory (and hundreds of zombie excel.exe's) by forcing the GC.

  7. dbt says:

    I'm more familiar with the java GC than the CLR one, but with generational collectors, objects can be collected well before your total heap usage reaches some obvious threshold.  Java calls them eden, survivor, and tenured, CLR apparently calls them simply 0, 1 and 2 generation.  Even if you're not allocating very many temporary objects, eventually the proportionally smaller generation 0 will get collected, and if your object is only weakly referred to it will be collected even if it could have been promoted instead.

    I'm surprised CLR doesn't have soft references, they're useful for things like in-memory caches.  weak references are primarily for implementing better resource cleanup than finalizers (in a concurrent environment; in a scoped evaluation of native resources obviously IDisposable/Closeable is a better pattern).

    Does CLR run finalizers before or after weak references are handled?  If finalizers run after, does it have the equivalent of phantom references, which are only enqueued after finalizers have run and the object is actually freed?

  8. Csaboka says:

    @dbt: The answer about your finalizer question is two links away: the older post Raymond has linked from today's one links to a short blog post explaining how WeakReferences work: blogs.msdn.com/…/588001.aspx . Apparently, the .Net WeakReference can work both as a Java WeakReference or a Java PhantomReference based on a constructor argument.

    As for Java SoftReferences: they do look like a neat feature, but you are really giving up a lot of control over your cache if you use them. All you get is a suggestion to the GC (the least recently used objects should be collected first) and a promise that all softly referenced objects are collected before an OutOfMemoryError is thrown. You cannot reason about how big your cache will actually be, or what its hit rate will look like, since those depend on GC implementation details. It is entirely possible that some completely different cache in a completely different library will make your cache starve because new objects are introduced to that one at a bigger rate than to yours. I'm sure there are cases where SoftReferences make sense despite these limitations, but you cannot just plug them into caches to make them work better.

  9. Cheong says:

    @DAVe3283Martin: Agreed. There is perfomance issue when lots of these zombie excel process got cleaned too. You see, when GC finally decided to "clean the house", most of these idling excel process already goes to page file. When they are "released from detention" you'll see lots of disk activity because they don't just quit and need to do some "final works".

    However we don't do GC collect, we just call quit, then goes out of scope and wait a few seconds, then try kill with the PID saved earlier to make sure it dies.

  10. AlexPaven says:

    @DAVe3283Martin: actually that problem can be solved without garbage collecting manually; it's been a while since I hit it but if I recall correctly you can use Marshal.ReleaseComReference (on all the COM objects you created, including the document and the actual Excel app object) and then the zombie process goes away. Doing a collect seems like taking an unnecessarily large sledgehammer to it :)

  11. Ooh says:

    @DAVe3283Martin and @AlexPaven: Yep, exactly. That's the "local solution to local problem" approach. Only downsides are that it's harder to get right and takes longer to develop, so it's the kind of solution many people don't want to know or care about (unfortunately).

  12. Mike Dimmick says:

    The .NET GC does not primarily run due to memory pressure (though that is one reason for it to run). The primary reason for GC to run is simply that a certain amount of allocation has occurred. Typically it's when the allocator reaches the end of its current allocation segment.

    A WeakReference is actually pretty simple: it is a reference to where an object currently lives. However, when GC is doing its mark-and-sweep, it won't use treat this as a reference to the object that should keep the object alive (unlike a normal reference). If anything else is still referencing the object, that reference causes GC to mark the object and move on.

    In the sweep phase, if the object is still marked, it may be moved (depending on whether GC is compacting the heap) and GC will update the WeakReference with the new location. If the object isn't marked through some other normal reference, the WeakReference will be zeroed.

    If you tell the WeakReference to 'track resurrection', and the object has a finalizer (and hasn't called GC.SuppressFinalize), GC will not immediately zero the weak reference in the sweep phase. It puts a pointer to the object's finalizer on the finalization queue. The finalizer thread then processes the finalization queue asynchronously. When it reaches this object, it checks whether any strong references exist: if so, it's taken back off the queue and allowed to live. Otherwise, the finalizer is run. If there are still no strong references, the WeakReference is zeroed. The object's memory itself remains in place until this area is next compacted.

    In a well-running system, there should be little to no delay between GC putting the object on the finalizer queue and the finalizer running, so really, this 'track resurrection' feature isn't that useful. The finalizer can itself create a strong reference back to the object, but it has to re-register for finalization. Care must be taken with that, as the finalizer will be called once for every time that GC.ReRegisterForFinalize is called (in real .NET, anyway: Compact Framework implements it differently and will only call the finalizer if GC.ReRegisterForFinalize has been called more recently than GC.SuppressFinalize).

  13. @AlexPaven and @Ooh – It has been a while since I worked on that code, but I spent several days trying to get it to work without a garbage collect (security policy frowned on manual GC). In the end, I got 2 other programmers to look over it, but we could never get excel to quit without a GC (or killing the process, which is worse, IMO), so we just got an exception to the security policy.

  14. Joshua says:

    @DAVe3283Martin: Did you try Excel.Quit()?

  15. John Doe says:

    @Mike Dimmick, now THAT's a comment!

  16. Random832 says:

    @DAVe3283Martin """ In the end, I got 2 other programmers to look over it, but we could never get excel to quit without a GC"""

    In my experience, it was probably some Range object that was used in the middle of an expression that kept the whole thing alive. Ended up having to break even simple expressions that worked with the Cells collection into three or four lines each not counting the cleanup code.

  17. Gabe says:

    Joshua: Maybe you can help out this guy: stackoverflow.com/…/c-sharp-excel-process-not-closing

  18. mafu says:

    I wondered about this a while ago (stackoverflow.com/…/39590 (ref)). Strangely enough, the MSDN library gives caches as an example of how to use the weak refs, even though that's a prime example of how *not* to use them. I've been very trusting to the MSDN so I was really confused.

Comments are closed.