61

I have not personally come across a situation where I've needed to use WeakReference type in .Net, but the popular belief seems to be that it should be used in caches. Dr Jon Harrop gave a very good case against the use of WeakReferences in caches in his answer to this question.

I've also often heard AS3 developers talk about using weak references to save on memory footprint but based on the conversations I've had it seems to add complexity without necessarily accomplishing the intended goal, and the runtime behaviour is rather unpredictable. So much so that many simply give up on it and instead manage the memory usage more carefully/optimize their code to be less memory intensive (or making the trade-off of more CPU-cycles and smaller memory footprint).

Dr Jon Harrop also pointed out in his answer that the .Net weak references are not soft, and there's an aggressive collection of weak references at gen0. According to MSDN, long weak references gives you the potential to recreate an object, but the state of the object remains unpredictable.!

Given these characteristics, I can't think of a situation where weak references would be useful, perhaps someone could enlighten me?

6
  • 3
    You've already outlined potential uses for it. Of course there are other ways to approach these situations, but there's more than one way to skin a cat. If you're looking for a bullet-proof "you should always use a WeakReference when X", I doubt you'll find one.
    – itsme86
    Jan 30, 2013 at 23:52
  • 2
    @itsme86 - I'm not looking for a bullet-proof use case, just ones that weak references are a good fit for and makes sense. The cache use case for instance, because weak references are so eagerly collected it's just going to cause more cache misses even when you have plenty of memory available
    – theburningmonk
    Jan 31, 2013 at 0:04
  • 4
    I'm a little disappointed this is getting some many votes to close. I wouldn't mind seeing an answer or some discussion about this (in b4 "Stack Overflow is not a forum"). Jan 31, 2013 at 0:05
  • @theburningmonk That is the offset in return for memory gain. In today's framework, it is doubtful that anyone would reach straight for the WeakReference tool even when implementing a cache since there are comprehensive caching systems readily available.
    – itsme86
    Jan 31, 2013 at 0:06
  • Here is an embarrassingly over-complicated example of using them (for the Weak Event Pattern that ta.speot.is describes below)
    – Benjol
    Jan 31, 2013 at 9:06

6 Answers 6

45

I've found legitimate practical applications of weak references in the following three real-world scenarios that actually happened to me personally:

Application 1: Event handlers

You're an entrepreneur. Your company sells a spark lines control for WPF. Sales are great but support costs are killing you. Too many customers are complaining of CPU hogging and memory leaks when they scroll through screens full of spark lines. The problem is their app is creating new spark lines as they come into view but data binding is preventing the old ones from being garbage collected. What do you do?

Introduce a weak reference between the data binding and your control so that data binding alone will no longer prevent your control from being garbage collected. Then add a finalizer to your control that tears down the data binding when it gets collected.

Application 2: Mutable graphs

You're the next John Carmack. You've invented an ingenius new graph-based representation of hierarchical subdivision surfaces that makes Tim Sweeney's games look like a Nintendo Wii. Obviously I'm not going to tell you exactly how it works but it all centers on this mutable graph where the neighbors of a vertex can be found in a Dictionary<Vertex, SortedSet<Vertex>>. The graph's topology keep changing as the player runs around. There's only one problem: your data structure is shedding unreachable subgraphs as it runs and you need to remove them or you'll leak memory. Luckily you're a genius so you know there is a class of algorithms specifically designed to locate and collect unreachable subgraphs: garbage collectors! You read Richard Jones' excellent monograph on the subject but it leaves you perplexed and concerned about your imminent deadline. What do you do?

Simply by replacing your Dictionary with a weak hash table you can piggyback the existing GC and have it automatically collect your unreachable subgraphs for you! Back to leafing through Ferrari adverts.

Application 3: Decorating trees

You're hanging from the ceiling of a cyclindrical room at a keyboard. You've got 60 seconds to sift through some BIG DATA before someone finds you. You came prepared with a beautiful stream-based parser that relies upon the GC to collect fragments of AST after they've been analyzed. But you realise you need extra metadata on each AST Node and you need it fast. What do you do?

You could use a Dictionary<Node, Metadata> to associate metadata with each node but, unless you clear it out, the strong references from the dictionary to old AST nodes will keep them alive and leak memory. The solution is a weak hash table which keeps only weak references to keys and garbage collects key-value bindings when the key becomes unreachable. Then, as AST nodes become unreachable they are garbage collected and their key-value binding is removed from the dictionary leaving the corresponding metadata unreachable so it too gets collected. Then all you have to do after your main loop has terminated is slide up back through the air vent remembering to replace it just as the security guard comes in.

Note that in all three of these real-world applications that actually happened to me I wanted the GC to collect as aggressively as possible. That's why these are legitimate applications. Everybody else is wrong.

5
  • 2
    Weak references won't work for application 2 if the unreachable subgraphs contain cycles. This is because a weak hash table usually has weak references to the keys, but strong references to the values. You'd need a hash table that maintains strong references to the values only while the key is still reachable --> see ephemerons (ConditionalWeakTable in .NET).
    – Daniel
    Aug 14, 2013 at 14:47
  • @Daniel Isn’t the GC supposed to be able to handle unreachable cycles? How would this not be collected when an unreachable cycle of strong references would be collected?
    – binki
    Jan 8, 2016 at 5:59
  • 1
    Oh, I think I see. I just assumed that ConditionalWeakTable is what applications 2 and 3 would use whereas some people in other posts actually use Dictionary<WeakReference, T>. No idea why—you’d always end up with a ton of null WeakReferences with values that cannot be accessed by any key regardless of how you do it. Ridik.
    – binki
    Jan 8, 2016 at 6:08
  • @binki: "Isn’t the GC supposed to be able to handle unreachable cycles? How would this not be collected when an unreachable cycle of strong references would be collected?". You have a dictionary keyed on unique objects that cannot be recreated. When one of your key objects becomes unreachable it can be garbage collected but the corresponding value in the dictionary won't be even thought it is theoretically unreachable because an ordinary dictionary will hold a strong reference to it, keeping it alive. So you use a weak dictionary.
    – J D
    Feb 27, 2017 at 11:13
  • @Daniel: "Weak references won't work for application 2 if the unreachable subgraphs contain cycles. This is because a weak hash table usually has weak references to the keys, but strong references to the values. You'd need a hash table that maintains strong references to the values only while the key is still reachable". Yes. You're probably better off encoding the graph directly with edges as pointers so the GC will collect it itself though.
    – J D
    Feb 27, 2017 at 11:14
21

Given these characteristics, I can't think of a situation where weak references would be useful, perhaps someone could enlighten me?

Microsoft document Weak Event Patterns.

In applications, it is possible that handlers that are attached to event sources will not be destroyed in coordination with the listener object that attached the handler to the source. This situation can lead to memory leaks. Windows Presentation Foundation (WPF) introduces a design pattern that can be used to address this issue, by providing a dedicated manager class for particular events and implementing an interface on listeners for that event. This design pattern is known as the weak event pattern.

...

The weak event pattern is designed to solve this memory leak problem. The weak event pattern can be used whenever a listener needs to register for an event, but the listener does not explicitly know when to unregister. The weak event pattern can also be used whenever the object lifetime of the source exceeds the useful object lifetime of the listener. (In this case, useful is determined by you.) The weak event pattern allows the listener to register for and receive the event without affecting the object lifetime characteristics of the listener in any way. In effect, the implied reference from the source does not determine whether the listener is eligible for garbage collection. The reference is a weak reference, thus the naming of the weak event pattern and the related APIs. The listener can be garbage collected or otherwise destroyed, and the source can continue without retaining noncollectible handler references to a now destroyed object.

1
13

Let me put this out first and come back to it:

A WeakReference is useful when you want to keep tabs on an object, but you DO NOT want your observations to prevent that object from being collected

So let's start from the beginning:

--apologies in advance for any unintentional offense, but I'm gonna back up to "Dick and Jane" level for a moment since one can never tell ones audience.

So when you've got an object X - let's specify it as an instance of class Foo - it CANNOT live on it's own (mostly true); In the same way that "No man is an island", there are only a few ways that an object can promoted to Islandhood - although it's called being a GC root in CLR speak. Being a GC Root, or having an established chain of connections/references to a GC root, is basically what determines whether or not Foo x = new Foo() gets garbage collected.

If you cannot walk your way back to some GC root either by heap or stack walking, you are effectively orphaned, and will likely be marked/collected next cycle.

At this point, let's look at some horrible-contrived examples:

First, our Foo:

public class Foo 
{
    private static volatile int _ref = 0;
    public event EventHandler FooEvent;
    public Foo()
    {
        _ref++;
        Console.WriteLine("I am #{0}", _ref);
    }
    ~Foo()
    {
        Console.WriteLine("#{0} dying!", _ref--);
    }
}

Fairly simple - it's not thread safe, so don't try that, but keeps a rough "reference count" of active instances and decrements when they are finalized.

Now let's look at a FooConsumer:

public class NastySingleton
{
    // Static member status is one way to "get promoted" to a GC root...
    private static NastySingleton _instance = new NastySingleton();
    public static NastySingleton Instance { get { return _instance;} }

    // testing out "Hard references"
    private Dictionary<Foo, int> _counter = new Dictionary<Foo,int>();
    // testing out "Weak references"
    private Dictionary<WeakReference, int> _weakCounter = new Dictionary<WeakReference,int>();

    // Creates a strong link to Foo instance
    public void ListenToThisFoo(Foo foo)
    {
        _counter[foo] = 0;
        foo.FooEvent += (o, e) => _counter[foo]++;
    }

    // Creates a weak link to Foo instance
    public void ListenToThisFooWeakly(Foo foo)
    {
        WeakReference fooRef = new WeakReference(foo);
        _weakCounter[fooRef] = 0;
        foo.FooEvent += (o, e) => _weakCounter[fooRef]++;
    }

    private void HandleEvent(object sender, EventArgs args, Foo originalfoo)
    {
        Console.WriteLine("Derp");
    }
}

So we've got an object that's already a GC root of it's own (well...to be specific, it'll be rooted via a chain straight to the app domain running this application, but that's another topic) that has two methods of latching on to a Foo instance - let's test it out:

// Our foo
var f = new Foo();

// Create a "hard reference"
NastySingleton.Instance.ListenToThisFoo(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Now, from the above, would you expect the object-that-was-once-referred-to by f to be "collectable"?

No, because there is another object now holding a reference to it - the Dictionary in that Singleton static instance.

Ok, let's try the weak approach:

f = new Foo();
NastySingleton.Instance.ListenToThisFooWeakly(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
// This should collect # 2 - you'll see a "#2 dying"
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Now, when we whack our reference to the-Foo-that-was-once-f, there are no more "hard" references to the object, so it is collectable - the WeakReference created by the weak listener won't prevent that.

Good use cases:

  • Event handlers (Although read this first: Weak Events in C#)

  • You've got a situation where you would cause a "recursive reference" (i.e., object A refers to object B, which refers to object A, also referred to as a "Memory Leak") (edit: derp, of course this isn't true)

  • You want to "broadcast" something to a collection of objects, but you don't want to be the thing keeping them alive; a List<WeakReference> can be maintained easily, and even pruned by removing where ref.Target == null

3
  • 1
    Regarding your second use case, the garbage collector handles circular references just fine. "object A refers to object B, which refers to object A" is definitely not a memory leak.
    – Joe Daley
    Jan 31, 2013 at 0:59
  • @JoeDaley I agree. The .NET GC uses a mark and sweep algorithm which (I believe I'm recalling this correctly) marks all objects for collection and then follows references from "roots" (references of objects on the stack, static objects), unmarking objects for collection. If a circular reference exists but none of the objects are accessible from a root, the objects are not unmarked for collection and are thus eligible for collection. Jan 31, 2013 at 1:08
  • 1
    @JoeDaley - You are both, of course, correct - was rushing it there towards the end...I'll edit that out.
    – JerKimball
    Jan 31, 2013 at 2:02
4

enter image description here

Like logical leaks which are really hard to track down while users just tend to notice that running your software for a long time tends to take more and more memory and get slower and slower until they restart? I don't.

Consider what happens if, when the user requests to remove application resource above, Thing2 fails to properly handle such an event under:

  1. Pointers
  2. Strong References
  3. Weak References

... and under which one of these such a mistake would likely be caught during testing, and which one wouldn't and would fly under the radar like a stealth fighter bug. Shared ownership is, more often than most, a nonsensical idea.

1

A very illustrative example of weak references used to good effect is the ConditionalWeakTable, which is used by the DLR (among other places) to attach additional "members" to objects.

You don't want the table to keep the object alive. This concept simply could not work without weak references.

But it kind of seems to me that all the uses for weak references came long after they were added to the language, since weak references have been part of .NET since version 1.1. It just seems like something you'd want to add, so that the lack of deterministic destruction won't back you into a corner as far as language features are concerned.

1
  • I've actually discovered that although the table uses the concept of weak references, the actual implementation does not involve the WeakReference type, as the situation is a lot more complex. It uses different functionality exposed by the CLR.
    – GregRos
    Jan 11, 2016 at 20:03
-2

If you have cache layer implemented with C# it's much better too put your data in cache as weak references, it could help to improve your cache layer performance.

Think that approach also could be applied to session implementation. Because session is long living object most of the time, it could be some case when you have no memory for new user. In that case it will be much better to delete some else user session object then throwing OutOfMemoryException.

Also, if you have a large object in your application (some big lookup table, etc), that should be used quite seldom and recreating of such an object isn't a very expensive procedure. Then better have it like a week reference to have a way to free your memory when you really need that.

3
  • 5
    but the problem with weak references (see the answer I referenced) is that they're very eagerly collected, and the collection is not linked to the availability of memory space. So you end up with more cache misses when there's no pressure on memory.
    – theburningmonk
    Jan 31, 2013 at 0:07
  • 1
    But for your second point about large objects, the MSDN doc states that whilst long weak references allow you to recreate an object, it's state remain unpredictable. If you're going to recreate it from scratch each time, why bother using a weak reference when you can just call a function/method to create it on demand and return a transient instance?
    – theburningmonk
    Jan 31, 2013 at 0:09
  • There's one situation where caching is helpful: If one will be frequently creating immutable objects, many of which will happen to be identical (e.g. reading many lines from a file that is expected to have many duplicates) each string will be created as a new object, but if a line matches another line to which a reference already exists, memory efficiency may be improved if the new instance is abandoned and a reference to the pre-existing instance is substituted. Note that this substitution is useful because the other reference is being held anyway. If it weren't code should keep the new one.
    – supercat
    Feb 27, 2013 at 22:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.