Wednesday, June 24, 2009

Don't use Integer keys with WeakHashMap

WeakHashMap relies on the key of an entry being garbage collected (due to being weakly referenced) so that the entire Map.Entry can be discarded.

However, if you use Integer as your map key, you can get suprising results when combined with autoboxing, due to a hidden cache of j.l.Integer objects embedded in java.lang.Integer itself.

This cache has been discussed in several other places (ie. http://www.owasp.org/index.php/Java_gotchas), particularly in respect to testing equality.

However, this cache of Integers from -128...127 will also mess with your WeakHashMap, pinning any elements in that range in memory, never to be released.

Here is a quick and dirty test demonstrating this issue:


public void testWeakHashMapWithIntKeys()
{
WeakHashMap<integer, stringbuffer> map = new WeakHashMap<integer, stringbuffer>();

map.put( 1, new StringBuffer("Test 1") );
map.put( 2, new StringBuffer("Test 2") );
map.put( 3, new StringBuffer("Test 3") );
map.put( 126, new StringBuffer("Test 126") );
map.put( 127, new StringBuffer("Test 127") );
map.put( 128, new StringBuffer("Test 128") );

System.out.println(map);

System.gc();

System.out.println(map);
}

You should get something like this:


{128=Test 128, 126=Test 126, 127=Test 127, 3=Test 3, 2=Test 2, 1=Test 1}
{126=Test 126, 127=Test 127, 3=Test 3, 2=Test 2, 1=Test 1}

Where the second line shows that all elements less than 128 have been retained.

This also applies to the other wrapper classes, java.lang.Byte, java.lang.Short, java.lang.Long, etc.

You can avoid this problem by not using autoboxing, and always invoking the new operator to create your keys. However, this seems to me to be error-prone. A more fool-proof strategy would be to wrap your Integer (or even a int!) in a custom class to avoid all this froo-fra.

Update: my google-foo found an example in "Pro Java Programming" (by Brett Spell), of how to not use WeakHashMap. His examples work by accident because the values he uses for the key are larger than 127.