DEV Community

Dayananda
Dayananda

Posted on

How to estimate Java object size

In Java, objects are the basic building blocks of any applications. When an object is created, memory is allocated from the heap to store its instance variables. Understanding how much memory an object consumes is important for optimizing memory usage and preventing OutOfMemoryErrors. It is important to optimize the memory consumption especially in cloud solutions.

Object size is calculated as Object Header size + variable size(for primitives) or refence size(for objects). But it is not that simple. Actual memory size consumed depends on data type, architecture, variable object refereed by other object or not(P1 & P2 refers to same country object), String/primitive pools etc...

In Java, primitive member variables are recommended and it consumes less space compare to wrappers. We will do the comparison and see how much memory can be saved with primitive data types.

Object memory size is always multiple of 8. JVM adds padding bytes to make it multiple of 8 if required. If object memory size comes as 12 byte length, then JVM adds 4 padding byte to make it multiple of 8. 12 byte + 4 byte = 16 byte. In this I will be using HotSpot JVM specification for measuring the sizes.


Size Metrics

To analyze size we use three metrics. Shallow size, Deep size, Retained size.

Shallow size In this metrics, we consider object size itself. If it is having reference to other objects, we consider reference size while calculating size metric.

Deep Size In this metrics, we consider object size and size of reference objects. 

Retained Size In this metrics, we consider object's shallow size and the objects are only referenced from this object. In other words, the retained size is the amount of memory that can be reclaimed by garbage-collecting this object. We don't consider objects if it is referred by more than one object.  

In real, Retained Size metric is very important and it is purely depends on data and object reference structure. It is tricky to calculate and complicate tasks if object(s) are referred by different instances.

Size Types Image


Elements influences object size

Object Header

Every object will have object header and it is based on oopDesc data structure. Object header will contain mark word and klass pointer.

Mark Word will contain information related to hash code, locking and GC information. The size of this is 4 byte and 8 byte in 32 and 62 bit architecture respectively.

Klass pointer will contain information related to class, modifiers and super class. The size of this is 4 byte in both architecture.

Object Header size would be 8 byte and 16 byte in 32-bit and 62-bit architecture respectively.

Normal Object Header size

Member Variables

  • Primitive data types

Primitive data types consumes fixed amount of memory size.

Primitive data types size

  • Primitive Wrappers

Primitive Wrappers will consumes additional memory size. For primitive wrappers memory size calculation is Primitive data type size + Object Header size + Padding bytes if required.

Primitive Wrappers size

  • Arrays

Arrays can be primitive or non primitive arrays. Memory size for arrays is sum of Object Header + (number of elements * reference for objects /primitive size for primitives) + length field (4 byte always). Reference is 4 byte size always. Length field will store the length of the array.

If we have Person[] with 36 length, then size is 12 + (36 * 4 (reference size) ) + 4 = 160 byte.

Array size non primitive

If we have byte[] with 36 length, then size is 12 + (36 * 1 (byte size)) + 4 = 52 + 4 padding = 56 byte.

Array size primitive

  • String

The size (shallow) of this String instance is 16 byte and 24 byte in 32 and 62 bit architecture respectively. It includes the 4 byte of cached hash code, 4 byte of char[] reference, and object header.

The actual string is stored as byte array and size of it is (1 (byte size) * number of characters) + 12 ( Object header) + 4 (length). For 36 length string will have (1*36) + 8 + 4 + 4 = 52 + 4 padding = 56 byte.

Total Retained/Deep size of String (of 36 length) is 24 byte + 56 byte = 80 byte.

String total size


Lets look into an example

We will first look into class without variables and later get into with variable. We will also look into member variables with wrappers and primitives. We will estimate the size and see how much memory we can save with primitive data types. I will be using VisualVM for profiling the data and 64-bit architecture system.

1. With out any member variables

Consider simple Person class without any variable.

public class Person{ 
}
Enter fullscreen mode Exit fullscreen mode

Now we will create 1000 objects of this and put it array.

public class MemoryMgmtController {
    int count = 1000;
    Person[] persons = new Person[count]; 
    public MemoryMgmtController() {
        for (int i = 0; i < count; i++) {
            persons[i] = new Person();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

As we know array memory size is sum of Object Header + (array size * 4 byte), So size of Person[] would be 16 byte (with 4 padding byte) + (1000 * 4 byte) = 4016 byte.

In addition to this, we have created 1000 Person objects which will have Object Header information (16 byte with 4 padding byte) by default. So it would be 16 * 1000 = 16000 byte.

So without any data, for simply creating 1000 objects itself has occupied 4016 + 16000 = 20016 byte (~20KB) memory.

Object without variables

2. With wrappers as member variables

Consider simple Person class with wrappers as member variable.

public class Person{ 
    public Boolean active;
    public Short country; 
    public Character gender; 
    public Integer id; 
    public Float salary;
    public Long pk; 
    public Double bonus; 
    public String name; 
}
Enter fullscreen mode Exit fullscreen mode

Now we will create 1000 objects with data and assign it to array.

public class MemoryMgmtController {
    int count = 1000;
    Person[] persons = new Person[count]; 
    public MemoryMgmtController() {
        for (int i = 0; i < count; i++) {
            Person person = new Person();
            person.setActive(Boolean.TRUE);
            person.setCountry((short) 12);
            person.setGender(GENDER);
            person.setId((int) i);
            person.setSalary((float) i);
            person.setPk((long) i);
            person.setBonus((double) i);
            person.setName(UUID.randomUUID().toString());
            persons[i] = person;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Size of Person[] would be same as earlier i.e 16 byte (with 4 padding byte) + (1000 * 4 byte) = 4016 byte.

But if we look at size of 1000 objects, it would increase. The reason we have added member variables which will occupy the size. We have added wrappers as member variable so Person object will hold the reference to wrapper objects.

Even though we say object header is 16 byte in 64-bit architecture, that is with 4 padding byte. While estimating, we consider 12 byte for object header, if required padding byte are added.

Size with wrapper member variables

Total Memory estimation

Total Estimation with wrappers

3. With primitives as member variables

When we use primitives they will consumer fixed size and directly part of Person. Person will not have any reference except String in our scenario.

With primitive variables

Using primitives instead of wrappers has saved nearly 45% memory in this scenario. This will vary based on variables and object structure.

Actual memory consumed will depend on so many other factors like retained size, duplicate strings, JVM implementation and many others. Having idea on high level estimation will help us to develop memory optimized applications.


Tips

  • Use primitives instead of wrappers where ever possible.
  • Use Trove library to have collections with primitives.
  • Consider UseCompressedOops java flag to improve the performance in 64-bit architecture.
  • Use Java Object Layout (JOL) tool to inspect memory layouts of object.

Happy Coding and Learning !!!

Please drop a comment if you have any question.

Top comments (4)

Collapse
 
a_mugisha profile image
A Mugisha

I have developed a tool to accurately measure object sizes at runtime and traverse object hierarchies as well. Maybe you are interested in it or have some feedback :) Github repo

Collapse
 
dayanandaeswar profile image
Dayananda

I am planning to develop similar. Will take a look into it and give you feedback. Thank you!

Collapse
 
monalisa profile image
lisa simp

How is knowing the size of objects useful to me? "Using primitives instead of wrappers has saved nearly 45% memory" - I knew this before reading the article. I know creating excessive objects is harmful to my application. How is this article any more useful?

Collapse
 
dayanandaeswar profile image
Dayananda

Here the focus is more on how to estimate shallow and deep object size as retained size depends on data. Knowing how to do size estimate who is not aware, this article will definitely help. Despite knowing primitives takes less memory, openapi generators still generate models with wrappers by default 😀.