Insights into Java Server Faces

2012/02/12

Reduced session size with empty Maps/Lists taking zero bytes space allocation

Filed under: Java, JSF — Hanspeter @ 23:03

It’s quite a while since my last post and this one is related to JSF but not only, it might also be usefull for other use cases. I will show a way to not allocate any space for empty collections (mainly Lists/Maps) unless a first entry is added – and that in a way that is fully transparent for the using part of the Map/List. This helps to keep managed beans containing data as small as possible and therefore allows to reduce session size.

Problem area description: let’s assume you have a list of appointments to manage. An appointment consists of some basic data like subject, description, date, location and so on and probably a few lists for required, optional attendees and maybe also some other contacts. Now some developer are lazy and just use empty inital Lists (ArrayList) or Maps (HashMap) with default initial size even though the list might be empty –  new ArrayList() allocates 80 bytes, new ArrayList(0) still takes 40 bytes, new HashMap() allocates 120 bytes, new HashMap(0) still allocates 48 bytes. Assuming we show a list of 100 appointments and the apointment has two lists which are empty in 80% of the appointments, we have 80 * 2 * 80 bytes then we have 12780 bytes wasted.

Classic way
The classic way to do lazy instantiation of the list does not work well here, because doing the instantiation in the getXxx()-method cannot distinguish if the list/map is acquired to add an entry or just to display it’s content. If it’s just for display, the list/map creation would not be necessary.
To come around this, one would have to use a special getXxxForUpdate() method which handles the lazy instantiation, but it’s obvious that this affects the API of how to access such lists/maps. So we need another solution that does not affect the way of how to access the list or map.

The idea: the instantiation of the list/map must be delayed until the first entry is added. Ths should be done in a way that is fully transparent to the user of the list or map. The using part get’s a fully functional List or Map implemantationn but first add()/put() operation leads to instantiation of the real List or Map using a callback method.

List example

Look at the simple example below – ListOwner is an object that has an empty list. In the getList() method as long as the list is null, return a new instance of CreateOnWriteList.java and implement the newList() callback method.  Now have a look at the add() method in CreatOnWriteList – it will call the newList() callback if the wrapped list is null. The important thing is, in the newList() callback you create the new List with minimal size to not waste space for not used entries, assign the instance to the local variable (ListOwner.list) and return the instance – because CreateOnWriteList also needs that instance to wrap. The wrapping allows to use the just created List as long as the CreateOnWriteList instance is referenced.

Next call to ListOwner.getList() will return the list that was created in the newList() callback and no additional wrapper or list replacement is in place anymore.

public class ListOwner {
  private List<String> list;

  public ListOwner() {
  }

  public List<String> getList() {
    if (list == null) {
      return new CreateOnWriteList<String>() {
        @Override
        public List<String> newList() {
          list = new ArrayList<String>(1); // init with minimal size to use less memory
          return list;
        }
      };
    }
    return list;
  }
}

Map example:

CreateOnWriteMap.java works exactly the same as CreatOnWriteList.java shown above. An according use example is:

public class MapOwner {
  private Map<String, String> map;

  public MapOwner() {
  }

  public Map<String, String> getMap() {
    if (map == null) {
      return new CreateOnWriteMap<String, String>() {
        @Override
        public Map<String, String> newMap() {
          map = new HashMap<String, String>(2); // size will grow to 2 on first put anyway
          return map; 
        }
      };
    }
    return map;
  }
}

Pros and cons:
The advantage of the delayed instantiation is obvious – no waste of space. The cost for that is – as long as no entry is on the list or map, each access to ListOwner.getList()/MapOwner.getMap() results in creation of a new CreateOnWriteList/-Map. These short living object lead to a minimal computational overhead, but a JVM with more freespace performs generally better so the saved space will pay off.

Hope this helps – cheers

Hanspeter

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Create a free website or blog at WordPress.com.