Tuesday, September 19, 2006

Something about Memory Leaks:Part-II

In my last post, I talked about how WeakHashMap can be used to avoid subtle memory leaks and believe me, I did not discover anything, its all there, all know about it and still I wrote just to refresh myself and all those who are like me.So!WeakHashMap can be useful but overuse can be problematic.And infact,improper use can lead to memory leak (beginners in java like me can often make such improper use).So let us use WeakHashMap in an improper way.

Suppose we want to keep record of all the ArrayList classes that will be created during a program run, so what we do is, we register the ArrayList class with our Registry implementation whenever a new instance of ArrayList class is created. And we also want to sort the ArrayList instances according to their size in the Registry.This can be very easily done by adding an instance initializer block in the java.util.ArrayList class as below:

public class ArrayList extends AbstractList

implements List, RandomAccess, Cloneable, java.io.Serializable

{

{

Registry.register(this);

}

private static final long serialVersionUID = 8683452581122892189L;

While running the program, we can prepend this modified ArrayList class in the bootclasspath (-Xbootclasspath/p:classfilelocation), thus the java program will use this class instead of the ArrayList class in the rt.jar.We will also have to override the hashCode() and equals() methods so that equals() method now checks for reference equality and hashcode can return the System.identityhashCode().

Ok!so our Registry class will somewhat look like:

import java.util.*;

public class Registry {

public static Map collectionRegistry = new WeakHashMap();

public static void registerCollection(Collection c) {

synchronized (collectionRegistry) {

collectionRegistry.put(c, null);//we are not interested in value

}
}


public static Set getCollectionRegistrySet() {

TreeSet cSet = new TreeSet(new CollectionComparator());

synchronized (collectionRegistry) {

cSet.addAll(collectionRegistry.keySet());

}

return cSet;

}

}

Here the Collection comparator will compare according to the size of the Collections .Thus getCollectionSet() will return a TreeSet of all the ArrayList instances created so far sorted according to the size of the ArrayLists.

And we are happy using the registry and we know all about the ArrayLists created.But if we look carefully,in the getCollectionSet() method we are doing cSet.addAll(Collection c) which copies all the elements in the collection c to cSet and we are returning this TreeSet.If we look in to the WeakHashMap implementation, it makes strong references of the keys while giving the keySet so that the keys don't disappear between hasNext and next call.And the end result is cSet holds strong references to all the keys of the WeakHashMap.The code which is using this Registry has to explicitly call getCollectionSet().clear() to release the strong references.

The moral of the story is if we are using WeakHashMap.keySet() and copying it somewhere else, we loose the whole essence of weak references .The design should be strong enough to avoid such situations( Don't think of using the WeakHashMap.keySet() directly, in multithreaded environment, you will mess everything up).

One more situation I can think of but I really doubt if someone can ever use such thing .I am talking about using String literals as Map keys .Look at the code below:

public class Test

{

public static void main(String[]args)

{

WeakHashMap map = new WeakHashMap();

1: String key1= "key1";

2: String key2=new String("key2");

3: map.put(key1,null);

4: map.put(key2,null);

5: key1=null;

6: System.gc();

7: int size=map.size();

8: key2=null;

9: System.gc();

10: int newSize=map.size();

}

}

We might expect that size should be 1 in line 7 and newSize should be 0 in line 10.But what we will actually get is size=2 in line 7 and newSize=1 in line 10.How? Key1 has been nullified and we explicitly ran Full GC? Then how it is 2? The problem is not with WeakHashMap but the way Strings has been designed and implemented.Key1 in line is a compile time constant, the String object is created during compilation and the JVM keeps a Strong reference to it by keeping it in an internally maintained String pool in the class file .Thus even though key1 has been nullified, JVM still holds a strong reference to it and GC cannot collect it .But key2 will be created at runtime,so GC will reclaim the memory after nullification and WeakHashMap will remove the mapping automatically .Also, had we used key2.intern(), the effect would have been the same as for key1.So we should never use String literals as keys to a WeakHashMap and also as referent to Reference Objects.


1 comment:

Anonymous said...

hi,
nice topic, keep going.

-nhm tanveer hossain khan (hasan)
http://hasan.we4tech.com