Friday, March 16, 2012

Java - Getting the memory size of an object.

Because the way Java language and the way JVMs are implemented, this apparently trivial task is more complex than it may seem specially when compared to C++ where all objects have a size() method. Java memory architecture is built to abstract the developer from direct manipulation of memory so that the developer can focus on more relevant tasks, this comes with a price.

There are 3 basic approaches you can take in order to do this all with their pros and cons:
  • Memory guess (Heap Size prior object creation - Heap Size after object creation = Object Size)
  • Java Instrumenting Query
  • Serialization

Memory Guess
This is the simplest approach because of the fact that you have a pretty easy access to the Heap Size via:
 Runtime.getRuntime().totalMemory();


The issue with this approach is that you cannot control what is being thrown into the heap at a given time, JVM is shared by a lot of processes that run in a given machine so this is a error prone approach that is not recommended.

Java Instrumenting Query

Starting on Java 5, all JVM have built in instrumentation features . This features can be used to monitor objects size in memory by creating an agent and setting it up for the JVM. This is by far the most precise method of getting the object's size from memory, but it's by no means the easiest/straight-forward. Another drawback is that this may change depending on the JVM you are using (the next example applies only to Oracle's Standard JVM 5 )

The agent looks something like this:

import java.lang.instrument.*;
import com.somepackage.SomeClass;

public class MyAgent {
  public static void premain(String args, Instrumentation inst) {
    SomeClass obj = new SomeClass();
    long size = inst.getObjectSize(obj);
    System.out.println("Bytes used by object: " + size);
  }
}
Then you need to register the agent to the JVM by initializing the application with the folowing parameters:

java -javaagent:agent.jar

That will cause the agent to be registered on the JVM instrumenting layer where it will be able to monitor object's size.
Next you can access these feature with something like:
public class MyAgent {
  private static volatile Instrumentation globalInstr;
  public static void premain(String args, Instrumentation inst) {
    globalInstr = inst;
  }
  public static long getObjectSize(Object obj) {
    if (globalInstr == null)
      throw new IllegalStateException("Agent not initted");
    return globalInstr.getObjectSize(obj);
  }
}
As you have certainly noticed this is no as intuitive as it may seem.
Serialization
This technique uses Java's serialization features to calculate the objects size. This is not the most precise way to do it because memory specific serialization works in a slightly different way than regular object serialization but its a pretty good approximation. One drawback of this approach is that it requires the object to be measured to be Serializable.
This is how a method that mesures the size would look like:
public long sizeOf(Serializable object){
 if (object == null) {
  return 0;
 }

 try{
  final ByteCountingOutputStream out = new ByteCountingOutputStream();
  new ObjectOutputStream(out).writeObject(object);
  out.close();
  return out.size();
 }catch (IOException e){
  if (log.isWarnEnabled()){
   log.warn("Unable to determine object size: " + object.toString(), e);
  }
  return -1;
 }
}
One of the greatest drawbacks of this simple approach is that OutputSreams in general are one of the most commonly known resource hogs in Java so you should treat them with care.