Weak Reference maintainability

441 Views Asked by At

I was reading up on weak references in java and sounds simple enough, if an object only has weak references on it, then it can be collected by the garbage collector. Except what happens if your reference becomes dead before you use the value?

Example:

Suppose I have a weak hashmap with the keys {1,2,3,4,5}, all with values of 1. Now suppose you have a random number generator for numbers in [1:10]. Now every time the number is gotten, it checks if it a key in the map and then gives a temporary strong reference to the key. So with this setup, you'll have some keys having strong references and thus stay in memory, but you also have the probability that some keys will become dead before being chosen.

If my intuition for weak hashmaps is correct, does that mean that the map will at some point be altered from its original state?

3

There are 3 best solutions below

4
VeeArr On

Trying to use Integer objects as keys for a WeakHashMap is likely to result in some strange behavior. To start with, the javadoc for WeakHashMap has the following note:

This class is intended primarily for use with key objects whose equals methods test for object identity using the == operator. Once such a key is discarded it can never be recreated, so it is impossible to do a lookup of that key in a WeakHashMap at some later time and be surprised that its entry has been removed. This class will work perfectly well with key objects whose equals methods are not based upon object identity, such as String instances. With such recreatable key objects, however, the automatic removal of WeakHashMap entries whose keys have been discarded may prove to be confusing.

Consider the following code:

    WeakHashMap<Integer, String> map = new WeakHashMap<>();
    Integer k = Integer.valueOf(9001);
    map.put(k, "OVER 9000!?");

    while (true)
    {
        System.out.println(map.get(k));
        Thread.sleep(100);
        k = Integer.valueOf(9001);
        System.gc();
    }

The loop will start by printing "OVER 9000!?", but after the first loop, the original key has been discarded (even if there's now a reference to a key that is equals to it). As a result, if that key object gets garbage collected, the entry will be removed from the map and the loop will begin printing "null" instead. Since we call System.gc(); after discarding the key, it's likely that this happens after a single loop.

That's not the end of the issues with using Integer as a WeakHashMap key, though. If you change the value 9001 above to 1, you'll find that the behavior changes! (Probably? This may be implementation-dependent.) Now, the entry never gets removed from the map. This is because of the integer cache--Integer.valueOf(1) always returns the same Integer instance, but Integer.valueOf(9001) creates a new Integer instance each time.

This second issue is specific to Integer, but the first actually applies to any scheme where you try to use keys where equals is not based on ==. And if equals is based on ==, then your question doesn't really apply--if you don't have a strong reference to the key anymore, it doesn't matter whether the value gets removed from the map because you no longer have a way to get to it--you can't recreate a key that uses identity-based equality.

0
Ondřej Fischer On

The purpose of WeakReference is to help with memory management. As you wrote, "if the object is not used normally" (there is no strong reference, in fact direct variable holding it), "then you don't need it any more" (it can be garbage collected). In case of weak hash map it aplies to the key, so you typically use it for caching of temporary associated data.

From that being said, it doesnt make sense to only put something into weak hash map without continue using the key as strong reference, because the collector can collect it immediately before you access it.

It may not (even System.gc() doesn't force the GC to run), but you cannot rely on it.

0
Holger On

This answer already addresses issues stemming from the use of types with value-based equality in a construct whose behavior depends on the identity of objects, like the reachability.

In short, when you are able to construct new objects with the same equality as the weakly reachable keys, it’s possible to detect the sudden removal of the keys.

However, you can also turn weakly reachable objects back to the strongly reachable state, e.g. by calling the get() method of a WeakReference or when iterating over the mappings of a WeakHashMap.

WeakHashMap<Object, Boolean> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, true);
WeakReference<Object> ref = new WeakReference<>(key);
key = null;
// now, the key object is only weakly reachable
key = ref.get();
// now, the key object might be strongly reachable again
// in that case, this statement will print true
System.out.println(map.get(key));

The construction of an object with a distinct identity and no overridden equals method via new Object() ensures that no other reference to the same object nor an equal object can exist. At one point of this code, the object is only weakly reachable, but then, it is made strongly reachable, with a very high likelihood.

It is possible that a garbage collection happens between these points and since all weak references to an object are cleared atomically, you can detect this situation by getting a null reference from get(). The likelihood for a garbage collection to happen right at this point is very low, though. That’s why the linked answer uses calls to System.gc() in-between, to raise the likelihood of the weak references to get cleared.

This is rather a contrived example, but helps addressing you question, “…does that mean that the map will at some point be altered from its original state?”.

If you use equal keys with a different identity or keys which are weakly reachable for some time, the map may get altered at some time, but there is no guaranty that this ever happens. This depends on when the garbage collector will run and actually discover the weak reachability of some objects. But typically, JVMs try to prevent garbage collection until there’s really a demand for it. So an application may run quiet a while without garbage collection at all. Further, if you do poll a mapping regularly, it may even happen, that the gc runs right at that point of time when the key is strongly reachable during the lookup.