Java memory footprint — part 1

Liviu Adrian Esanu
METRO SYSTEMS Romania
5 min readJul 5, 2017

Understanding the way memory works is both useful and practical. To fully grasp the more advanced concepts takes a lot of time and effort. However, understanding the biggest issues and developing a set of best practices requires a much smaller investment.

Memory footprint of an Integer object

With that in mind, consider the following lines of code:

public static void main(String[] args) {
Integer i = new Integer(10);
}

When this code is executed, how much memory gets allocated on the second line ?

In other words, since an int takes 32 (at least on a 32 bits JVM) our question asks how much memory does an Integer object use. It is obvious that the memory used be an Integer object will need to contain the int payload, thus used 32 bits. More than that, it will also use some memory for the fields that the Integer class inherits from Object class.

Take a look at the image below, and see that Integer class inherits 3 fields from Object class:

The first field is a reference to a Class object. Indeed every object in Java has a reference to the class that object is an instance of. This reference uses 32 bits.

The second field is a 32 bits field containing flags and the identity hash code. As a note, the first bit is the shape bit, it marks if the object is a normal object or an array. The identity hash code is usually the initial memory address where the object was first allocated. This value is cached here, and used for other operations, for example the hashCode() method.

The third field contains locks. It is a well known fact that each Java object must contain a lock that is used by the synchronization mechanism.

Thus, a simple instantiation of an Integer object will use 4 * 32 = 128 bits of memory, since the Integer object has four 32 bit fields.

Memory footprint of an int array

Let us also take a look at the memory footprint of an int[]:

int[] a = {10};

As expected, this array object will contain similar fields inherited from the Object class:

We see the class, pointer, the flags, the locks and an extra 32 bits field, the size field. The value of this field is the number of values contained by the int array, the length of the array. The actual int values follow, in a contiguous fashion, each value using another 32 bits. In total, the array “a” above will use 5 * 32 = 160 bits of memory.

Memory footprint of a String object

Now let’s see how much memory is used by a String object:

String s = new String(“myString”);

It is clear that a String object will inherit some fields from Object class and also needs to store the actual characters contained by that string. Its memory footprint will look like in the following image:

As we can see, a String object stores the usual class pointer, flags, locks fields and some additional new ones.

The hash field, is a 32 bit field containing the hash code value of that string. Since strings are immutable this hash code will never change, thus it makes sense to compute it once then store it.

The count field value is the number of characters in the String object.

The offset field contains the value of the first index of storage. Namely, since the characters are stored in a char array, the offset tells us the index in that array and the count tells the number of character the string actually has. In case our program uses a large string and a smaller sub string of it, this mechanism ensures that both of them will share the same char array.

The value field is a 32 bits reference to a char array.

The char array itself contains the class pointer, flags, locks and size fields, just like the int array above, and in addition to them, the actual chars in our string. These chars are stored on 16 bits, thus we have 2 chars stored in a 32 bit portion of contiguous memory.

The total memory used by our line of code is 7 * 32 + 8 * 32 = 224 + 256 = 480 bits, grouped in two parts of contiguous memory.

Memory footprint of String concatenation

An interesting case is in the code below:

String s1 = “string 1”;
String s2 = “string 2”;
String s3 = s1 + s2;

What happens in the third line of code, and what is the memory footprint of the code above ?

The first thing we must remember is how Java treats string concatenation. Namely, at compile time, the third line above will be replaced by the following:

s3 = new StringBuilder(String.valueOf(s1)).append(s2).toString();

Therefore we can see that memory will be allocated for strings s1, s2 and s3 and also for the StringBuilder object. This means that the memory footprint will look something similar to this image:

Thus 4 objects that each contain a reference to a char array will be created (s1, s2 , s3 and a reference of the StringBuilder object) and 4 char arrays.

This is actually not this simple, due to the Java String literal pool mechanism. In order to conserve memory, all string literal values are stored in a special memory area called the String literal pool.

One other thing to notice is that the char arrays belonging to s1, s2 and s3 must be immutable, since s1, s2 and s3 are immutable. The underlining char array for the StringBuilder object should be mutable, since StringBuilder is a mutable class. This however is not in the scope of this article.

Conclusions and best practices

  • Integer objects use 4 times as much memory than a 32 bit int. Whenever all other things are equal, use a int[] instead of an Integer[]
  • For object arrays there is an overhead (extra fields) to the actual useful information. The overhead is constant though this means that he larger the payload (larger array) the less wasted memory
  • String objects share some similarities with char arrays. They are immutable and there exist memory saving mechanisms like the Java String literal pool
  • Use StringBuilder instead of string concatenation. The compiler uses them anyway, therefore it is much better to do it in a manner where you have full control and transparency over what happens

For further conclusions please take a look at the part 2 of this article: Java memory footprint — part 2

--

--

Liviu Adrian Esanu
METRO SYSTEMS Romania

Senior Java developer at METRO Systems Romania. 9+ years of Java experience. Founder of “Bucharest Competitive Programming” group.