The road so far….

July 5, 2010

Maps can avoid java.lang.OutOfMemoryError

Filed under: java — Tags: — Rahul Sharma @ 9:19 pm

Often we try to use java Maps for some kind of caching purpose.  Usually we keep adding data to Map either in form of new entries or keep updating old entries with new data. In any case Map starts to bloat up,  keeping data in it infinitely or until we remove it. In such a case objects keep piling up on the Java Heep space and we eventually get an java.lang.OutOfMemoryError: Java heap space exception.

But we can avoid such a situation and create an extensible Map that can be used to hold information without Out-Of-Memory exception,  let me show you how.

The Cause of the Problem:

The problem is the Out-Of-Memory exception. The JVM continuously monitors the throughput of the system. If it finds that too much of time is being spent on the garbage collection process then it will throw the Out-Of-Memory exception. But the good point is that it does GC before throwing such an exception.

If we can devise such a system where some of the Objects, currently being referenced, gets removed then we can have a system where under load the system performance degrade a bit  but it will not completely crash out.

Assumption:

This also brings out the following assumption which I have taken while using a Map that can be resilient to out-of-memory situations.

  • Since the map is used for some kind of caching so information stored in the map can be re-created at any point of time, if not available.

Possible Solutions:

In order to look for a solution I have tried to create a map with custom keys and values and then put a lot of information in it, and see if it fails or not.


class MyKeyObject {
 Long key;e
 String string;
 boolean b;
 File file;
 public boolean equals(MyKeyObject obj) {
   return key.equals(obj.key);
 }
}

class MyValueObject {
 Long value;
 String string;
 boolean b;
 File file;
 public boolean equals(MyValueObject obj) {
  return value.equals(obj.value);
 }
}

public class FailTheMap {
 Map<MyKeyObject, MyValueObject> map;

 void tryToFail(int maxValue) {
  long i = 0l;
  while (i < maxValue) {
   MyKeyObject key = new MyKeyObject();
   MyValueObject value = new MyValueObject();
   key.key = value.value = new Long(i);
   key.string = value.string = "QWERTYASDFG--->" + i;
   key.b = value.b = (i % 2 == 0);
   key.file = value.file = new File("QWERT" + i);
   map.put(key, value);
   i++;
  }
  System.out.println("final size:" + map.size());
 }
}

Solution One : Use WeakHashMap

The Java Map library provides the WeakHashMap that can be used directly for storing information in such a scenario. Maps usually store information that are strongly referenced and when GC happens it does not collect objects with strong references. The WeakHashMap keeps keys in the form of WeakReference, which allow them to be garbage collected  if there is no other reference , except the one in the map. Since the Keys are garbage collected so the values corresponding to them can not be referenced from the Map and thus they also can be garbage collected if there is no other reference to it.


@Test
 public void tryTofailWeakhashmap() {
  FailTheMap failTheMap = new FailTheMap();
  failTheMap.map = new WeakHashMap<MyKeyObject, MyValueObject>();
  failTheMap.tryToFail(Integer.MAX_VALUE);
  System.out.println("DID NOT FAIL!!! size is :" + failTheMap.map.size());
 }

The map gets garbage collected often and thus the data in it is kept limited, depending on the heap available. We can query this map for data, if not found we will be required to re-create it, and then may be put in it. Since the map is often garbage collected there is a performance hit on the same.

BUT……..

If  we have a look at the key/value object  we see that there are references to the File object. Possibly we would like to close these references or there can be other clean up task to do. But how can we do it ?

There is no API from the map that can allow us to do a clean on the entries that are going to be removed, so I tried to do it in the finalize method of the Object class. But putting some kind of IO close/ Print(System.out) operation there,  makes the process of garbage collection slower and thus the objects are not collected at the same rate at which they are being created. So it throws an Out-Of-Memory error after quite some time.

class MyValueObject {
  //fields as above
  @Override
  protected void finalize() throws Throwable {
   System.out.println("-----"+value);
  }
}

Solution Two: Use LinkedHashMap

Java provides the LinkedHashMap that can be used as a LRU cache in such situation. The map can be customized to hold only specified amount of data(entries). Any more additions in  it will result in removal so some data from the Map. There removed data can be the one inserted the first or the one accessed the least among the data. In order to do we need to extend the LinkedHashMap and then override the  removeEldestEntry method.

What happens under the hood is when data is inserted in the Map, then it check if you want to remove the eldest entry. The default implementation does no do any thing and inserts more data in the map. We can customise the method to do removal, at times depending on our situation.

class CustomMap<K, V> extends LinkedHashMap<K, V> {
 private static final long serialVersionUID = 1L;
 long maxPermissbleSize
 @Override
 protected boolean removeEldestEntry(java.util.Map.Entry eldest) {
  return this.size() > maxPermissbleSize;
 }
}
@Test
public void tryTofailCustomMap() {
 FailTheMap failTheMap = new FailTheMap();
 CustomMap<MyKeyObject, MyValueObject> customMap = new CustomMap();
 customMap.maxPermissbleSize = 100000;
 failTheMap.map = customMap;
 failTheMap.tryToFail(Integer.MAX_VALUE);
 System.out.println("DID NOT FAIL!!! size is :" + failTheMap.map.size());
}

Here I have customized the map to hold only specified number of enties, and when it reached the size it removes one entry to insert new entry and thus the map size remains limited. Now again there is some performance hit on the map as it checks again and again if there is some information for removal.

The map here provides back the entry that will be removed, the argument  of the removeEldestEntry, so in this map we can do some kind of close/clean up operations that are required and will not have an impact on garbage collection.

class CustomMap<K, V> extends LinkedHashMap<K, V> {
 private static final long serialVersionUID = 1L;
 long maxPermissbleSize
 @Override
 protected boolean removeEldestEntry(java.util.Map.Entry eldest) {
  boolean result= this.size() > maxPermissbleSize;
  if(result){
   // DO Soemthing on the eldest entry
  }
  return result;
}
Integer.
Advertisements

3 Comments »

  1. If you override equals you must override hashcode or your objects may not get stored properly in a hashmap.

    Comment by David — July 7, 2010 @ 12:23 am

  2. If you really want to store a lot of entries without worrying about OOMEing ur app, a caching solution like ehcache would do a better job at it. You can also swap part of data to disk or distribute it to other processes over the network then. Since the interface is mostly a map interface, it should be simple enough to replace an existing Map with a cache.

    Full disclosure: I work for the company that makes ehcache.

    Comment by Saravanan Subbiah — July 8, 2010 @ 7:36 am

  3. commons collections has it’s own impl of a lru map so no need to have your own impl unless you want something more fancy.

    Comment by Steven — July 8, 2010 @ 6:49 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: