Hot questions for Operating WeakHashMap in Guava

Top 10 Java Open Source / Guava / Operating WeakHashMap

Is there java.util.concurrent equivalent for WeakHashMap?

Question: Can the following piece of code be rewritten w/o using Collections.synchronizedMap() yet maintaining correctness at concurrency?

Collections.synchronizedMap(new WeakHashMap<Class, Object>());

i.e. is there something from java.util.concurrent one can use instead? Note that merely replacing with

new ConcurrentHashMap<Class, Object>(new WeakHashMap<Class, Object>()));

obviously won't work

Answer: Guava's CacheBuilder class allows you to do this easily

CacheBuilder.newBuilder().weakKeys().build()

Note that this changes key equality semantics to be == instead of .equals() which will not matter in your case of using Class instances but is a potential pitfall.


Why WeakHashMap holds strong reference to value after GC?

Question: Key object in WeakHashMap became weakly reachable. And map should be remove the entry after GC. But a strong reference to the value object remains. Why?

The same behavior is observed with guava weakkeys map.

Expected output:

...
refKey.get = null
refValue.get = null

But I get output:

map.keys = []
map.values = []
map.size = 0
refKey.get = null
refValue.get = (123)

Code:

public class Test {

    static class Number {
        final int number;
        public Number(int number) { this.number = number; }
        public String toString() { return "(" + number + ")"; }
    }

    static class Key extends Number {
        public Key(int number) { super(number); }
    }

    static class Value extends Number {
        public Value(int number) { super(number); }
    }

    public static void main(String args[]) {

        //Map<Key, Value> map = new MapMaker().weakKeys().makeMap();
        Map<Key, Value> map = new WeakHashMap<>();

        Key key = new Key(1);
        Value value = new Value(123);

        map.put(key, value);

        WeakReference<Key> refKey = new WeakReference<>(key);
        WeakReference<Value> refValue = new WeakReference<>(value);

        key = null;
        value = null;

        System.gc();

        System.out.println("map.keys = " + map.keySet());
        System.out.println("map.values = " + map.values());
        System.out.println("map.size = " + map.size());
        System.out.println("refKey.get = " + refKey.get());
        System.out.println("refValue.get = " + refValue.get());

    }
}

Answer: The WeakHashMap contains Map.Entry instances which reference the key using a WeakReference

When the GC happens, the entries which now reference absent keys are not magically removed from the map: they're still present until cleared, which is why the value is also still present and has not been collected yet.

If you want your value to be garbage collectable, you should interact with the WeakHashMap, e.g. by asking for its size() or doing a lookup.

Note that it means the value cannot be collected until a second garbage collection.

If I remember correctly, it works more or less the same way in Guava.


Can someone explain to me when it is useful to use MapMaker or WeakHashMaps?

Question: I have read many people really like the MapMaker of Google Guava (Collections), however I cannot see any good uses of it.

I have read the javadoc, and it says that it behaves like ConcurrentHashMap. It also says new MapMaker().weakKeys().makeMap() can almost always be used as a drop-in replacement for WeakHashMap.

However, reading the javadocs of ConcurrentHashMap and WeakHashMap makes me wonder when it is useful to use it? It seems to me that you cannot have a guarantee that whatever you put in the map will be there, or have I misunderstood?

Answer: Weak references are useful, if you don't want to (or cannot afford to) retain an object indefinetly in memory. Consider the following use case: you need to associate information with classes. Now, since you are running in an environment, where classes might get reloaded (say, a Tomcat, or OSGi environment), you want the garbage collector to be able to reclaim old versions of a class as soon as it deems safe to do so.

An initial attempt to implement this, might look like

class ClassAssoc {
    private final IdentityHashMap<Class<?>,MyMetaData> cache = new ...;
}

The problem here is: this would keep all classes in the cache member forever (or at least, unless they are manually removed), forcing the garbage collector to retain them indefinitly, including everything referenced from the class (static member values, class loader information, ...)

By using weak references, the garbage collector can reclaim old version of the class as soon as no other references to it (usually instances) exist. On the other hand: as long as such references exist, the value is guaranteed to be also reachable from the weak reference object, and thus, is a valid key in the cache table.

The thing about MapMaker is that there are many options for the kind of map you build, which enables those maps to serve many purposes.

  1. Dirk gives a good example of a use for weak keys.
  2. Soft values are useful for caching, as you can cache values in the map without worrying about running out of memory since the system is free to evict entries from the cache if it needs memory.
  3. You can choose to have entries expire after a certain amount of time. This is also useful for caching, since you may want certain data cached for a specific period of time before doing an expensive operation to update it.
  4. One of my favorite things is making a computing map. A computing map uses a Function<K, V> to automatically retrieve the value associated with a given key if it isn't already in the map. This combines well with soft values and/or expiration times. After an entry is evicted by the map (due to memory demand or expiration), the next time the value associated with that key is requested it will automatically be retrieved and cached in the map once more.