How Java works

Pick a topic, write code and watch what happens inside, step by step.

Memory

Collections

Concurrency

Language and JVM

Topics

Objects in memory

The header: 12 bytes in every object

Every object on the heap starts with a header: an 8-byte mark word and a 4-byte class pointer, which says what class the object is.

The mark word does several jobs: the identity hash, the GC age and the lock state, all in the same 64 bits. When the object is locked, those bits become a pointer.

Press the buttons and watch the bits change. Biased locking has been gone since Java 18, so it is not shown here.

Fields and padding

Fields are not stored in the order you write them. The JVM places large primitives first, then smaller ones, and references last. Each field goes into the smallest gap it fits.

Each field starts at an offset divisible by its size: a long at a multiple of 8, an int at a multiple of 4. The object size must be divisible by 8.

Build your own class and watch where the bytes go. The 4 bytes right after the header are often filled by an int or a reference.

Arrays

An array header takes 16 bytes: the mark word, the class pointer and a 4-byte length. The elements follow it.

A primitive array holds the values itself. An Integer[] holds only references, and each Integer is a separate 16-byte object on the heap.

That is why 100 numbers take 416 bytes in an int[] and 2016 bytes in an Integer[].

What collections cost

Collections cannot hold primitives, so every number becomes an Integer. Each element also gets internal objects: a Node in HashMap, a doubly linked Node in LinkedList.

1000 numbers take 4 KB in an int[] and 56 KB in a HashSet<Integer>, 14 times more. With millions of elements, that difference matters.

Libraries such as Eclipse Collections and fastutil have collections for primitives. They take about as little space as an int[].

The 32 GB line

On a 64-bit JVM a pointer is 8 bytes, but when the heap is small the JVM squeezes it into 4 bytes: compressed oops.

4 bytes hold 2³² different values. The JVM multiplies them by 8, because objects start at addresses divisible by 8. That reaches up to 32 GB.

Raise the heap from 31 GB to 32 GB and watch the reference size. Java 25 also has compact object headers, where the header takes 8 bytes instead of 12.