Object Memory Layout in Java

Naveen AnamaneniUpdated · 5 min read

Every Java object on the heap has a layout: a fixed header that the JVM uses for its own bookkeeping, followed by the object's fields, with padding added so that the total size is a multiple of 8 bytes. Arrays also store their length. Knowing the layout lets you estimate how much memory your objects really use, which matters when a program holds millions of them. In this lesson, you will learn each part of an object's layout and measure real sizes from a running program.

The Parts of an Object

Header, fields, then padding to a multiple of 8 bytes
PartSize (64-bit JVM, default settings)Holds
Mark word8 bytesIdentity hash code, lock state, garbage collector age bits
Class pointer4 bytes (compressed)Which class this object is
Array length4 bytes, arrays onlyNumber of elements
Fieldslong/double 8, int/float 4, short/char 2, byte/boolean 1, references 4 (compressed)The object's data
Padding0 to 7 bytesRounds the size up to a multiple of 8

On heaps under 32 GB, the JVM uses compressed references ("compressed oops"): object references take 4 bytes instead of 8. The JVM may also reorder fields (larger first) to reduce padding.

Example: Measuring Real Object Sizes

Let's see an example that creates 100,000 objects of each class, then asks the JVM's class histogram (the same data as jcmd <pid> GC.class_histogram) how many bytes each instance uses.

ObjectSizes.java▶ Try it
import java.lang.management.ManagementFactory;
import javax.management.ObjectName;
 
public class ObjectSizes {
    static class Empty { }
    record Point(int x, int y) { }
    record Student(String name, int marks, boolean passed) { }
    record Wide(long id, double score) { }
 
    static long bytesEach(String className) throws Exception {
        String histogram = (String) ManagementFactory.getPlatformMBeanServer()
            .invoke(new ObjectName("com.sun.management:type=DiagnosticCommand"),
                    "gcClassHistogram", new Object[] {null},
                    new String[] {String[].class.getName()});
        for (String line : histogram.split("\n")) {
            String[] p = line.trim().split("\\s+");
            if (p.length >= 4 && p[3].equals(className)) {
                return Long.parseLong(p[2]) / Long.parseLong(p[1]);
            }
        }
        return -1;
    }
 
    public static void main(String[] args) throws Exception {
        Object[] keep = new Object[400_000];
        for (int i = 0; i < 100_000; i++) {
            keep[i] = new Empty();
            keep[100_000 + i] = new Point(i, i);
            keep[200_000 + i] = new Student("Navika", i, true);
            keep[300_000 + i] = new Wide(i, i);
        }
        for (String name : new String[] {"Empty", "Point", "Student", "Wide"}) {
            System.out.printf("%-8s %d bytes%n", name + ":",
                bytesEach("ObjectSizes$" + name));
        }
        System.out.println(keep.length + " objects kept");
    }
}
Output
Empty:   16 bytes
Point:   24 bytes
Student: 24 bytes
Wide:    32 bytes
400000 objects kept

Explanation:

  • Empty: a 12-byte header, no fields, padded to 16.
  • Point: 12 + two ints (8) = 20, padded to 24.
  • Student: 12 + a reference (4) + an int (4) + a boolean (1) = 21, padded to 24. The "Navika" string is a separate shared object, not counted here.
  • Wide: 12 + a long (8) + a double (8) = 28, padded to 32.

Wrappers and Arrays

The same rules explain why boxed values are expensive: an Integer is a 12-byte header plus a 4-byte int, so 16 bytes to hold 4 bytes of data, plus a 4-byte reference wherever it's stored. An int[1000] is 16 bytes of header and length plus 4,000 bytes of data, while a List<Integer> of 1,000 values uses the list's array of references plus 1,000 separate Integer objects.

Common Mistakes

Assuming an object costs only the size of its fields. The header adds 12 bytes, and padding adds up to 7 more.

Counting referenced objects as part of an object's size. References are 4 bytes; the referenced objects are measured separately.

Assuming 8-byte references everywhere. Compressed references are the default for heaps under 32 GB.

Practice

  1. Easy: Predict the size of a record with one long and one int, then add it to ObjectSizes to check.
  2. Harder: Measure the size of Integer objects by keeping 100,000 Integer.valueOf(i + 1000) values, and compare with 100,000 ints in an array.

Quick check: Object memory layout

4 questions
1How big is the object header with default settings on a 64-bit JVM?
2Why is an empty object 16 bytes, not 12?
3How big is a compressed object reference?
4Which tool reports bytes per class in a running JVM?

Key Takeaways

Was this tutorial helpful?

Lesson 436 of 482