What an object really costs

JVM · lesson 5 of 34 · 3 min read

Every object carries a 12 byte header, and the total is rounded up to a multiple of eight.

Open this lesson in the learning hub

Key points

  • An object is a header plus its fields. The header is 12 bytes: an 8 byte mark word and a 4 byte class pointer.
  • The mark word carries the identity hash and lock state. The class pointer is how the JVM knows what type it is looking at.
  • The total is padded to a multiple of 8, so even new Object() costs 16 bytes.
  • References are 4 bytes rather than 8 on heaps under 32 GB. That is compressed oops, and it is on by default.
  • Arrays add 4 more bytes for the length. Wrappers are mostly overhead: an Integer is 16 bytes to carry 4 bytes of data.
  • This is why a million-entry HashMap costs many times more than a million-slot int[].

Example

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;

public class Main {

    static class Point { int x; int y; }
    static class Node  { Object next; }

    static Object sink;              // publishing it stops the JIT skipping the allocation

    public static void main(String[] args) {
        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        if (!(bean instanceof com.sun.management.ThreadMXBean sun)
                || !sun.isThreadAllocatedMemorySupported()) {
            System.out.println("this VM does not expose allocation counters");
            return;
        }
        System.out.println("bytes on the heap, measured over 100,000 allocations each:");
        System.out.println("  new Object()   " + bytesEach(sun, 0) + "   header only");
        System.out.println("  new Node()     " + bytesEach(sun, 1) + "   header + 1 reference");
        System.out.println("  new Point()    " + bytesEach(sun, 2) + "   header + 2 ints");
        System.out.println("  new int[4]     " + bytesEach(sun, 3) + "   header + length + 4 ints");
        System.out.println();
        System.out.println("Header is 12 bytes: 8 byte mark word + 4 byte class pointer,");
        System.out.println("then the whole object is rounded up to a multiple of 8.");
        System.out.println();
        System.out.println("identity hash, straight out of the mark word: "
                + Integer.toHexString(System.identityHashCode(new Point())));
    }

    static long bytesEach(com.sun.management.ThreadMXBean sun, int kind) {
        int n = 100_000;
        long start = sun.getCurrentThreadAllocatedBytes();
        for (int i = 0; i < n; i++) {
            switch (kind) {
                case 0 -> sink = new Object();
                case 1 -> sink = new Node();
                case 2 -> sink = new Point();
                default -> sink = new int[4];
            }
        }
        return (sun.getCurrentThreadAllocatedBytes() - start) / n;
    }
}

Small objects are mostly header. Count objects, not just fields.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the JVM course, and every lesson in it is listed on the JVM contents page.