WeakHashMap, canonical cache and value recycling?

Hi All,

I’m looking to implement a canonical caching strategy in a few places. WeakHashMap would normally be ideal for this, but I need a variant that lets me grab the values as the keys are collected (for a variety of reasons, including recycling in some cases). As far as I’m aware, this isn’t possible with WeakHashMap. Does anyone know of a good performing alternative that allows for grabbing values as they come up to be cleared?

Thanks, Neil

Use a ReferenceQueue when adding keys/values to the map

example:



ReferenceQueue<MyData> mQueue = new ReferenceQueue<MyData>();
Map<String, MyData> map = new HashMap();

class MyData extends SoftReference/WeakReference {
  YourKey key;
  MyData(Referent r, YourKey key) {
   super(r, mQueue);
   this.key = key;
  }
}

to add to cache

void put(YourKey key, Referent r) {
  map.put(new MyData(r, key));
}

then to read the recycled objects

while (true) {
 MyData data = mQueue.poll();
 if (data == null) break;
 ...process here
}


HTH

  • David

WeakHashMap is very unlikely to be a good choice. Its non-strongly-referenced references will be cleared on every GC (in Suns/Oracles JVM), as opposed to only during a full GC, which you probably want. It can easily run a couple of times per second, which kinda invalidates it as a cache.

What you probably need is a SoftHashMap, which (annoyingly) is not in the standard API. Pick a 3rd party one.

Thanks both for your replies.

Do you mean basically reimplement WeakHashMap, which I’m happy to do but am hoping not to have to, or just wrapping the keys in a second weak reference? I’m pretty sure the second wouldn’t work because by the time I try and get the value out of the map it’ll already be nulled.

I don’t understand your example - aren’t you calling map.put() with a single argument?

I could have put bets on someone saying this! ;D

No, I want a WeakHashMap! I understand it’s not much use as a cache in itself, though neither usually would a SoftHashMap be - you want a map to soft referenced values. In fact, I have a slight feeling of deja vu - I’m pretty sure we’ve had that conversation in another thread. :slight_smile:

There’s a good article here on this http://www.codeinstructions.com/2008/09/weakhashmap-is-not-cache-understanding.html

So why do I want a WeakHashMap? Quote from same article on “What is the WeakHashMap good for?”

This is basically what I’m trying to do - associate a cached object (most likely soft referenced) with a particular object - should have been clearer earlier! I want the keys to be GC’d as soon as possible, because once the key is unreferenced, the value is unreachable. The problem is, I want to get the values added to a queue on clearing the key so that I can do other things with them, rather than just having them set to null.

class WeakHashMap<K, V> {
	ReferenceQueue<KeyValuePair> Q = new ReferenceQueue();
	HashMap<K, KeyValuePair<K,V>> M = new HashMap();
	class KeyValuePair<K, V> extends WeakReference<K, V> {
		V value;
		
		KeyValuePair(K key, V value) {
			super(K, Q);
			this.value = value;
		}
	}
	
	public void put(K key, V value) {
		M.put(key, new KeyValuePair(K, V));
	}
	
	public V get(K key) {
		KeyValuePair KVP = M.get(key);
		if (KVP != null) {
		 return KVP.value;
		}
		return null;
	}
	
	public void recycle() {
		while (true) {
			KeyValuePair KVP = Q.poll();
			if (KVP != null) {
				V value = KVP.value;
				// do something with value
			}
		}
	}
	
	// etc..
	
}

HTH

  • David

Thanks, David, but isn’t that storing the value as a weak reference and not the key? I need the same semantics as WeakHashMap, with the key being wrapped in a weak reference.

The key is the weak reference, look at the KVP constructor

Btw typo:


super(K, value);

should be


super(key, Q);

I know you’re storing the key as a weak reference in the KVP object.

However, surely doing -

 public void put(K key, V value) {
        M.put(key, new KeyValuePair(K, V));
    }

means the key is stored as a strong reference in the map as well, so will never be collected.

well then you could just override hashCode and use this


public int hashCode() {
 K key = get();
 if (key == null) {
  return 0;
 }
 return key.hashCode();
}

put:


public void put(K key, V value) {
KeyValuePair kvp = new KeyValuePair(key, value);
       M.put(kvp, kvp);
   }

full:


class WeakHashMap<K, V> {
    ReferenceQueue<KeyValuePair> Q = new ReferenceQueue();
    HashMap<K, KeyValuePair<K,V>> M = new HashMap();
    class KeyValuePair<K, V> extends WeakReference<K, V> {
        V value;
         
        KeyValuePair(K key, V value) {
            super(key, Q);
            this.value = value;
        }
		
		@Override
		public int hashCode() {
			K key = get();
			if (key == null) {
				return 0;
			}
			return key.hashCode();
		}
    }
     
    public void put(K key, V value) {
		KeyValuePair kvp = new KeyValuePair(K, V);
        M.put(kvp, kvp);
    }
     
    public V get(K key) {
        KeyValuePair KVP = M.get(key);
        if (KVP != null) {
         return KVP.value;
        }
        return null;
    }
     
    public void recycle() {
        while (true) {
            KeyValuePair KVP = Q.poll();
            if (KVP != null) {
                V value = KVP.value;
                // do something with value
            }
        }
    }
     
    // etc..
     
}

[quote]This is basically what I’m trying to do - associate a cached object (most likely soft referenced) with a particular object - should have been clearer earlier! I want the keys to be GC’d as soon as possible, because once the key is unreferenced, the value is unreachable. The problem is, I want to get the values added to a queue on clearing the key so that I can do other things with them, rather than just having them set to null.
[/quote]
Maybe go back a step? Make a finalize() method on the object itself, and have THAT put the value on the queue. The GC will check finalize() as well as clearing the key from your cache. I’m not sure of the exact timing, but the two events should be directly tied together, yes?

If only some of the objects are in your cache, encode that as a property to be consulted by finalize().

Could be cleaner than going through a lot of rigamarole with special cache handling?

In any case, though, it seems the exact timing of what you are doing is “passive” and things have to be handled carefully to avoid concurrency issues.

Hi Phil,

Thanks for your response.

Normally I’d agree with you, but this involves a number of situations where I can’t (easily) edit the key objects concerned. One situation involves objects from a 3rd party library, and the other would involve circular module dependencies.

You did trigger a related thought though (at least I think it’s related, it might be what you meant!) - I might be able to get away with wrapping the current value object in a lightweight wrapper that uses finalize().

I also may have managed to find a 3rd-party solution - http://guava-libraries.googlecode.com/svn/tags/release09/javadoc/com/google/common/collect/MapMaker.html#evictionListener(com.google.common.collect.MapEvictionListener) - first thing I’ve found ready-made that seems to offer a solution. Have no idea what the performance will be like, and a bit wary it’s beta tagged. Other suggestion still welcomed.

And I realise all the added complexity of this may turn out just not to be worth it and I’ll end up sticking with a plain WeakHashMap! :slight_smile:

I’m pretty sure that would still not work unless you also overrode equals(), and that would end up in equals() having asymmetric qualities which are sure to blow up somewhere!

Thanks and best wishes, Neil

Sure, maybe a wrapper makes sense. Whatever it takes to make the objects themselves be the instigators, rather than having to monitor their presence in your cache.

Mostly, I was just thinking that maybe there was an event “upstream” that could solve your problem rather than dealing with it from the POV of the cache. It felt from the description that things were getting kind of hairy.

HOWEVER, for lunch, I just finished chapter 7 in “Java Concurrency in Practice” and they strongly recommend avoiding finalizers. They say it’s better to have and use a close() method for an object than to rely on a finalizer. Or, if there is a “try” involved, making good use of the finally block.

So, I take back the finalizer suggestion, and leave it as suggesting a look upstream for an alternative moment to put the objects in your queue. (A wrapper with a close() method that manages the queue entry?)

For lunch, I tend to prefer a sandwich … :stuck_out_tongue:

Yes, finalize() introduces some interesting concurrency issues, so probably isn’t the right approach. Unfortunately, I can’t use the explicit close() approach here. I’m talking about various usages in a library, where objects are passed in from user code - the only way to know for definite that the user object will not be passed in again, and cached information is definitely no longer required, is when they go out of scope. My understanding is that this is exactly what WeakHashMap was written for.

I’ll probably experiment with the Guava approach as this will also allow the cached information to be soft referenced and the possibility of LRU within one map - see if it’s all worth the complexity.