Showing posts with label strong-reference. Show all posts
Showing posts with label strong-reference. Show all posts

Sunday, 17 April 2011

Weakhashmap : When value depends on key

Consider the following code:

import java.util.Map;
import java.util.WeakHashMap;

public class TestWeakHeakHashMap
{
private String name = new String("java");
private Map cache = new WeakHashMap<String, DependentObject>();

public void testMethod()
{
cache.put(name, new DependentObject("1", name));

//Discard the strong reference to the key
name = null;
while (true) {
System.gc();
/**
          * Verify Full GC with the -verbose:gc option
            Since there is no strong reference to the key, it is assumed that the
            entry has been removed from the WeakHashMap
          */
System.out.println(cache.size());
}
}

private class DependentObject
{
private String id;
private String name;

public DependentObject(String id, String name)
{
this.id = id;
this.name = name;
}
}
}

Now when the testMethod() is run what do you expect the output to be? Since the strong reference to key is discarded, we assume that the entry from the map would be removed, and map would be empty after a full GC.
But that does not happen though.

Let us see what was the put operation on the WeakHashMap.

cache.put(name, new DependentObject("1", name));


Here the value DependentObject was holding the key name. This would mean that the value always strongly refers to the key, and hence the key would never be garbage collected. The entry would always remain the map.

This is what WeakHashMap API says - "The value objects in a WeakHashMap are held by ordinary strong references. Thus care should be taken to ensure that value objects do not strongly refer to their own keys, either directly or indirectly, since that will prevent the keys from being discarded."

Weakhashmap : Using string from literal pool as key

Consider the following code snippet:
public class TestWeakHashMap
{
private String str1 = new String("newString1");
private String str2 = "literalString2";
private String str3 = "literalString3";
private String str4 = new String("newString4");
private Map map = new WeakHashMap();

private void testGC() throws IOException
{
map.put(str1, new Object());
map.put(str2, new Object());
map.put(str3, new Object());
map.put(str4, new Object());

/**
        * Discard the strong reference to all the keys
        */
str1 = null;
str2 = null;
str3 = null;
str4 = null;

while (true) {
System.gc();
/**
            * Verify Full GC with the -verbose:gc option
            * We expect the map to be emptied as the strong references to
            * all the keys are discarded.
            */
System.out.println("map.size(); = " + map.size() + " " + map);
}
}
}

What do we expect the size of the map to be after full GC? I initially thought it should be empty. But it turned out to be 2.

Look at the way the four Strings are initialized. Two of them are defined using the 'new' operator, whereas the other two are defined as literals. The Strings defined using the 'new' operator would be allocated in the Java heap, but the Strings defined defined as literals would be in the literal pool.
The Strings allocated in the literal pool (Perm Space) would never be garbage collected.
This would mean that String 'str2' and 'str3' would always be strongly referenced and the corresponding entry would never be removed from the WeakHashMap.

So next time you create a 'new String()' , put it as a key in a WeakHashMap, and later intern() the String, beware - Your key will always be strongly referenced.

Invoking intern() method on a String will add your String to the literal pool if some other String equal to this String does not exist in the pool
private String str5 = (str4+str1).intern();