menu

Sunday, February 1, 2015

Actual Memory Consumption of Java Objects

 Introduction:

     Today we'll be talking about the general memory consumption of java objects on the Heap, although the normal situation may sometimes change, i.e; the JVM may choose not the put a Thread local object on to the Heap instead put it to the stack. 

 General Memory Usage of Java Objects:

     Although the memory usage may changes depending on the JVM's vendor, JVM's type as 32 bit-64 bit or UseCompressedOops VM argument, the general formula for a Java object is like below.

 An object header (object class information, status flags about the synhronization locked and the reachability of the object, ID of the class) 8 bytes for 32 bit JVM and 12 bytes for 64 bit UseCompressedOops  enabled JVM, 16 bytes for 64 bit non compressed JVM + Memory for primitives according to their size (See below) + Memory for reference to the member pointer (4 bytes for 32 bit JVM and 64 bit UseCompressedOops  enabled JVM, 8 bytes for 64 bit non compressed JVM) + padding (a wasted space after the object data to make every object start at a standart address space which is a multiple of 8, so rounded to the next multiple of 8)
Be careful, all the middle calculations will be rounded to the next multiple of 8, not the total calculation. That means the outer object is rounded individually while the fields of that objects are rounded individually.

byte, boolean     1 byte
  short, char         2 bytes
  int, float            4 bytes
  long, double      8 bytes

Java Primitive Size

Here we see boolean type is holding 1 byte although it is required just one single bit. This is just for easy access to the class in memory. If we holded 1 bit for the boolean type, we would have been deal with the exact position of the boolean type each time we read or write data, but in this case we use a byte offset and the implementation is easy now.
When you need boolean array, you can use the BitSet implementation of Java language to be able to hold 1 bit for each boolean variable of the array.

 Note: In this write you can see the memory space retained for primitives depending on the place where they defined. Here the point is, if we declare the primitive as class variables they will hold the sizes above , however for the local variables they will be defined independently and they may retain larger space then their requirements. That's because the smallest unit of the memory for a JVM is changing between 32 bit and 64 bit versions.For example,some JVM's may hold a 1 byte variable on the 4 byte smallest unit for 32 bit and 8 byte unit for 64 bit.To be really able to hold 1 byte for a byte you should use array of byte , which will be holded on the heap and the array members will be reached by the reference of the array, so can hold really 1 byte.

 General Memory Usage of Java Array Objects:

     Arrays are normal java objects so their memory usage include the previous section's steps plus a memory space for the length of the array. This space is 4 bytes for 32 bit JVM's and 64 bit UseCompressedOops  enabled JVM, 8 bytes for 64 bit non compressed JVM). 

 Note: For most of the JVM implementations UseCompressedOops  is enabled by default, so you don't need to the anything to enable it. You can check whether its enabled or not using the below java code and iterating over the arguments list.

RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();

List<String> arguments = runtimeMxBean.getInputArguments();

Now we'll show you some examples for memory usage of java objects. But, before that we need to find out a way to measure the memory usage. In this post, we use java Instrumentation class to be able to measure the size of objects.

 How to Find the Memory Usage Programmatically Using Instrumentation:

     We'll use java.lang.instrument.Instrumentation class to get the size of the java objects. To do that follow the following instructions.

1.) Create a java project including a class that include the following static field and the following methods named premain and shallowSizeOfObject.

private static Instrumentation instrumentation = null;

public static void premain(String args, Instrumentation paramInstrumentation) {
instrumentation = paramInstrumentation;
}

public static long shallowSizeOfObject(Object obj) {
return instrumentation.getObjectSize(obj);
}

2.) Create a MANIFEST.MF file including the following definition.

Premain-Class: your_pck_name.Your_Class_Name

3.) Export the java project as a jar file using the MANIFEST.MF created in step 2.

4.) Add the jar created in step 3 to your project class path.

5.) In your project that has reference to the jar created in step 3, run the class that include a main method with the following VM argument.

-javaagent:your_jar_name.jar

That's it, when you run your program, since you tell that the jar file including the Instrumentation code is an java agent (using the javaagent VM argument) the JVM will automatically run the premain method and set the Instrumentation object to your static variable.Then you can use the getObjectSize(obj) method to get the shallow size of a java object which will include header + primitive variables total size + member pointers like we mentioned in the previous part of this post.
However if you have inner arrays or objects in your object, the getObjectSize(obj) will not give you memory usage of this arrays or objects. That's why we call our method as shallowSizeOfObject.Now ,let's see how can we get the deep memory size of a java object.

 Deep Memory Usage of Java Objects:

     To get the real retained memory space of a java object, we'll use reflection api to access the inner arrays and objects. Also we should use some techniques to skip the pooled objects, like String, Integer etc and to skip primitive and static variables.We then recursively call our deepSizeOfObject method until we finished all the fields of our object and the fields of the all super classes of our object.
Below is the last version of our SizeUtil class including the methods added to get those functionalities.

package sizeutil;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Map;

public class SizeUtil {

private static boolean SKIP_POOLED_OBJECTS = false;

private static Instrumentation instrumentation = null;

public static void premain(String args, Instrumentation paramInstrumentation) {
instrumentation = paramInstrumentation;
}

/**
* Calculates size
* @param obj
*            object to calculate size of
* @return object size
*/
public static long shallowSizeOfObject(Object obj) {
if (SKIP_POOLED_OBJECTS && isPooled(obj))
return 0;
return instrumentation.getObjectSize(obj);
}

private static boolean isPooled(Object paramObject) {
if ((paramObject instanceof Comparable)) {
if ((paramObject instanceof Enum)) {
return true;
}
if ((paramObject instanceof String)) {
return paramObject == ((String) paramObject).intern();
}
if ((paramObject instanceof Boolean)) {
return (paramObject == Boolean.TRUE) || (paramObject == Boolean.FALSE);
}
if ((paramObject instanceof Integer)) {
return paramObject == Integer.valueOf(((Integer) paramObject).intValue());
}
if ((paramObject instanceof Short)) {
return paramObject == Short.valueOf(((Short) paramObject).shortValue());
}
if ((paramObject instanceof Byte)) {
return paramObject == Byte.valueOf(((Byte) paramObject).byteValue());
}
if ((paramObject instanceof Long)) {
return paramObject == Long.valueOf(((Long) paramObject).longValue());
}
if ((paramObject instanceof Character)) {
return paramObject == Character.valueOf(((Character) paramObject).charValue());
}
}
return false;
}

/**
* Calculates deep size
* @param obj
*            object to calculate size of
* @return object deep size
*/
public static long deepSizeOfObject(Object obj) {

Map<Object, Object> previouslyVisited = new IdentityHashMap<Object, Object>();
long result = deepSizeOf(obj, previouslyVisited);
previouslyVisited.clear();
return result;
}

private static boolean skipObject(Object obj, Map<Object, Object> previouslyVisited) {
if (SKIP_POOLED_OBJECTS && isPooled(obj))
return true;
return (obj == null) || previouslyVisited == null || previouslyVisited.containsKey(obj);
}

@SuppressWarnings("rawtypes")
private static long deepSizeOf(Object obj, Map<Object, Object> previouslyVisited) {
if (skipObject(obj, previouslyVisited)) {
return 0;
}
previouslyVisited.put(obj, null);

long returnVal = 0;
// get size of object + primitive variables + member pointers
// for array header + len + if primitive total value for primitives
returnVal += SizeUtil.shallowSizeOfObject(obj);

// recursively call all array elements
Class objClass = obj.getClass();
if (objClass == null)
return 0;
if (objClass.isArray()) {
if (objClass.getName().length() != 2) {// primitive type arrays has length two, skip them (they included in the shallow size)
int lengthOfArray = Array.getLength(obj);
for (int i = 0; i < lengthOfArray; i++) {
returnVal += deepSizeOf(Array.get(obj, i), previouslyVisited);
}
}
} else {
// recursively call all fields of the object including the superclass fields
do {
Field[] objFields = objClass.getDeclaredFields();
for (int i = 0; i < objFields.length; i++) {
if (!Modifier.isStatic(objFields[i].getModifiers())) {// skip statics
if (!objFields[i].getType().isPrimitive()) { // skip primitives
objFields[i].setAccessible(true);
Object tempObject = null;
try {
tempObject = objFields[i].get(obj);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block

} catch (IllegalAccessException e) {
// TODO Auto-generated catch block

}
if (tempObject != null) {
returnVal += deepSizeOf(tempObject, previouslyVisited);
}
}
}
}
objClass = objClass.getSuperclass();
} while (objClass != null);
}
return returnVal;
}
}

 Examples of Memory Usage:

     Here are some examples in a single TestSizeOf class about java object's memory usage. We use the SizeUtil class mentioned in the previous section in our examples. The examples are run on 64 bit Java version 1.8.0.25 where as I mentinoned UseCompressedOops enabled automatically. So our objects have 12 bytes header, 4 bytes ref to member pointers, 4 bytes ref to array elements, and extra 4 bytes for array objects to hold the length. Remember to add the -javaagent:sizeOfObjects.jar as VM argument before run your main program.

package test;

import sizeutil.SizeUtil;

public class TestSizeOf {
private String innerStr;
private int innerInt;

public TestSizeOf(String innerStr, int innerInt) {
this.innerInt = innerInt;
this.innerStr = innerStr;
}

public static void main(String[] args) {
boolean bo1 = true;
System.out.println("Retained space for boolean bo1 = true --> " + SizeUtil.deepSizeOfObject(bo1));
byte b1 = 3;
System.out.println("Retained space for byte b1 = 3 --> " + SizeUtil.deepSizeOfObject(b1));
short sh1 = 3;
System.out.println("Retained space for short sh1 = 3 --> " + SizeUtil.deepSizeOfObject(sh1));

int i1 = 3;
System.out.println("Retained space for int i1 = 3 --> " + SizeUtil.deepSizeOfObject(i1));

long l1 = 3L;
System.out.println("Retained space for long l1 = 3L --> " + SizeUtil.deepSizeOfObject(l1));

char c1 = 'A';
System.out.println("Retained space for char c1 = 'A' --> " + SizeUtil.deepSizeOfObject(c1));

float f1 = 3f;
System.out.println("Retained space for float f1 = 3f --> " + SizeUtil.deepSizeOfObject(f1));
double d1 = 3;
System.out.println("Retained space for double d1 = 3 --> " + SizeUtil.deepSizeOfObject(d1));

Object o = new Object();
System.out.println("Retained space for Object o = new Object() --> "+ 
SizeUtil.deepSizeOfObject(o));

String st1 = "A";
System.out.println("Retained space for String st1 = \"A\" --> " + SizeUtil.deepSizeOfObject(st1));

byte[] arrByte = new byte[20];
System.out.println("Retained shallow space for byte[] arrByte = new byte[20] --> " +  SizeUtil.shallowSizeOfObject(arrByte));
System.out.println("Retained deep space for byte[] arrByte = new byte[20] --> " +  SizeUtil.deepSizeOfObject(arrByte));

Byte[] arrByteObj = new Byte[] { 3, 4, 5 };
System.out.println("Retained shallow space for Byte[] arrByteObj = new Byte[] { 3, 4, 5 } --> " +    SizeUtil.shallowSizeOfObject(arrByteObj));
System.out.println("Retained deep space for Byte[] arrByteObj = new Byte[] { 3, 4, 5 } --> " +  SizeUtil.deepSizeOfObject(arrByteObj));

int[] arrInt = new int[] { 0 };
System.out.println("Retained shallow space for int[] arrInt = new int[] { 0 } --> " +  SizeUtil.shallowSizeOfObject(arrInt));
System.out.println("Retained deep space for int[] arrInt = new int[] { 0 } --> " +  SizeUtil.deepSizeOfObject(arrInt));

long[] arrLong = new long[] { 0 };
System.out.println("Retained shallow space for long[] arrLong = new long[] { 0 } --> " +  SizeUtil.shallowSizeOfObject(arrLong));
System.out.println("Retained deep space for long[] arrLong = new long[] { 0 } --> " +  SizeUtil.deepSizeOfObject(arrLong));

int[][] arrIntTwoDim = new int[5][5]; 

for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
arrIntTwoDim[i][j] = i * j;
}
}
System.out.println("Retained shallow space for int[][] arrIntTwoDim = new int[5][5] --> " +  SizeUtil.shallowSizeOfObject(arrIntTwoDim));
System.out.println("Retained deep space for int[][] arrIntTwoDim = new int[5][5] --> " +  SizeUtil.deepSizeOfObject(arrIntTwoDim));

String[] arrStr = new String[4];
arrStr[0] = "1";
arrStr[1] = "2";
arrStr[2] = "3";
arrStr[3] = null;
System.out.println("Retained shallow space for String[] arrStr = new String[4] --> " +  SizeUtil.shallowSizeOfObject(arrStr));
System.out.println("Retained deep space for String[] arrStr = new String[4] --> " +  SizeUtil.deepSizeOfObject(arrStr));

TestSizeOf myTest = new TestSizeOf("test", 11111111);
System.out.println("Retained shallow space for TestSizeOf myTest = new TestSizeOf(\"test\", 11111111) --> " + SizeUtil.shallowSizeOfObject(myTest));
System.out.println("Retained deep space for TestSizeOf myTest = new TestSizeOf(\"test\", 11111111) --> " + SizeUtil.deepSizeOfObject(myTest));

Object obj = new String("test");
System.out.println("Retained shallow space for Object obj = new String(\"test\") --> " +  SizeUtil.shallowSizeOfObject(obj));
System.out.println("Retained deep space for Object obj = new String(\"test\") --> " +  SizeUtil.deepSizeOfObject(obj));
}

}

Here is the output of the program; 

Retained space for boolean bo1 = true --> 16
Explanation: 12 bytes header + 1 byte for boolean = 13 rounded to 16 bytes
Retained space for byte b1 = 3 --> 16
Explanation: 12 bytes header + 1 byte for byte= 13 rounded to 16 bytes
Retained space for short sh1 = 3 --> 16
Explanation: 12 bytes header + 2 bytes for short = 14 rounded to 16 bytes
Retained space for int i1 = 3 --> 16
Explanation: 12 bytes header + 4 bytes for int = 16 no need to round
Retained space for long l1 = 3L --> 24
Explanation: 12 bytes header + 8 bytes for long = 20 rounded to 24 bytes
Retained space for char c1 = 'A' --> 16
Explanation: 12 bytes header + 2 bytes for 1 char = 14 rounded to 16 bytes
Retained space for float f1 = 3f --> 16
Explanation: 12 bytes header + 4 bytes for float= 16 no need to round
Retained space for double d1 = 3 --> 24
Explanation: 12 bytes header + 8 bytes for double= 20 rounded to 24 bytes
Retained space for Object o = new Object() --> 16
Explanation: 12 bytes header  = 12 rounded to 16 bytes
Retained space for String st1 = "A" --> 48
Explanation: (for String)12 bytes header + 4 bytes ref to char[] + 4 bytes hash int value = 20 rounded to 24 bytes 

(for char[]) 12 bytes header + 4 bytes for length of char[] + 1(1 character String)*2 bytes =18 bytes rounded to 24 bytes
-> so 24 + 24 = 48 bytes
Retained shallow space for byte[] arrByte = new byte[20] --> 40
Explanation: 12 bytes header + 4 bytes for length of byte[]  + 20 (20 bytes in array) * 1 bytes = 36 rounded to 40 bytes
Retained deep space for byte[] arrByte = new byte[20] --> 40
Explanation: 12 bytes header + 4 bytes for length of byte[]  + 20 (20 bytes in array) * 1 bytes = 36 rounded to 40 bytes
Retained shallow space for Byte[] arrByteObj = new Byte[] { 3, 4, 5 } --> 32
Explanation: 12 bytes header + 4 bytes for length of byte[]  + 3 (ref to each array element) * 4 bytes = 28 rounded to 32 bytes
Retained deep space for Byte[] arrByteObj = new Byte[] { 3, 4, 5 } --> 80
Explanation: 12 bytes header + 4 bytes for length of byte[]  + 3 (ref to each array element) * 4 bytes = 28 rounded to 32 bytes
+
3 (for 3 array elements) * 16 bytes = 48 bytes
-> so 32 + 48 = 80 bytes
Retained shallow space for int[] arrInt = new int[] { 0 } --> 24
Explanation: 12 bytes header + 4 bytes for length of int [] + 1(1 int in array) * 4 bytes = 20 rounded to 24 bytes
Retained deep space for int[] arrInt = new int[] { 0 } --> 24
Explanation: 12 bytes header + 4 bytes for length of int [] + 1(1 int in array) * 4 bytes = 20 rounded to 24 bytes
Retained shallow space for long[] arrLong = new long[] { 0 } --> 24
Explanation: 12 bytes header + 4 bytes for length of int [] + 1(1 long in array) * 8 bytes = 24 rounded to 24 bytes
Retained deep space for long[] arrLong = new long[] { 0 } --> 24
Explanation: 12 bytes header + 4 bytes for length of int [] + 1(1 long in array) * 8 bytes = 24 rounded to 24 bytes
Retained shallow space for int[][] arrIntTwoDim = new int[5][5] --> 40
Explanation: (for outer array)12 bytes header + 4 bytes for  length of int [][]  + 5(ref to 5 inner array) * 4 bytes = 36 rounded to 40 bytes
Retained deep space for int[][] arrIntTwoDim = new int[5][5] --> 240
Explanation: (for outer array)12 bytes header + 4 bytes for  length of int [][]  + 5(ref to 5 inner array) * 4 bytes = 36 rounded to 40 bytes
+
(for inner arrays) 5(5 inner array) * (12 bytes  header +  4 bytes for  length of int [] + 5 * 4 bytes = 36 rounded to 40 bytes) = 200 bytes no need to round
-> so 40  + 200 = 240 bytes
Retained shallow space for String[] arrStr = new String[4] --> 32
Explanation: 12 bytes header + 4 bytes for  length of String + 4 (4 String in array) * 4 bytes = 32 bytes no need to round
Retained deep space for String[] arrStr = new String[4] --> 176
Explanation: 12 bytes header + 4 bytes for  length of String + 4 (4 String in array) * 4 bytes = 32 bytes no need to round
+
3 (4 th value is null so has no memory space) * 48 bytes (1 String with 1 character is 48 bytes as explained before) = 144 bytes
so -> 32 + 144 = 176 bytes
Retained shallow space for TestSizeOf myTest = new TestSizeOf("test", 11111111) --> 24
Explanation: 12 bytes header + 1 (ref to String field of the class) * 4 bytes + 4 bytes for int field = 20 bytes rounded to 24 bytes
Retained deep space for TestSizeOf myTest = new TestSizeOf("test", 11111111) --> 72
Explanation: 12 bytes header + 1 (ref to String field of the class) * 4 bytes + 4 bytes for int field = 20 bytes rounded to 24 bytes
+
(for String field) 12 bytes header + 4 bytes for ref of char[] + 4 bytes for hash int value = 20 rounded to 24 bytes 
+
(for char[] of String) 12 bytes header + 4 bytes length + 4 * 2 bytes = 24 bytes no need to round
so-> 24 + 24 = 48 bytes
so the final result-> 24 + 48 = 72 bytes
Retained shallow space for Object obj = new String("test") --> 24
Explanation: 12 bytes header + 4 bytes for ref of char[] + 4 bytes for hash int value = 20 rounded to 24 bytes 
Retained deep space for Object obj = new String("test") --> 48
Explanation: 12 bytes header + 4 bytes for ref of char[] + 4 bytes for hash int value = 20 rounded to 24 bytes 
+
(for char[] of String) 12 bytes header + 4 bytes length + 4 * 2 bytes = 24 bytes no need to round
so-> 24 + 24 = 48 bytes 

You can see the explanations in the below of each output.

Although we use primitive types in the first few examples, they are boxed to their wrapper classes when we call the deepSizeOfObject method. Normally we don't boxed them, we just use them like that just for explanation.

All the objects has 12 bytes header in the example. The wrapper objects has 12 bytes header + primitive size memory usage. Arrays has extra 4 bytes for their length.

The two dimensional arrays are just array of arrays.So the outer array has separate header and all the inner arrays has separate headers also.

The TestSizeOf  class has two fields as String and int. They are calculated separately in the deepSizeOfObject method.

Conclusion:

     We see the real retained memory space of java objects. Although the current state of the object like whether it is garbage collected or it's synchronization locked is contended may affect the size of the object on the heap, if we get a general understanding of  how much our objects will occupy, we can reconsider our java code to get less space and more performance. We'll be talking about memory consumption of String and StringBuilder objects and some suggestions on using String related objects in the next post.

You can download the source code from here.

No comments:

Post a Comment