Java path · Objects and types · Topic 5 of 12

Stack, heap and object layout

Not started

The JVM Specification describes a few run-time memory areas. Each thread has its own stack of frames, one per method call in progress, holding local variables and the operand stack. All objects and arrays live on one heap shared by every thread, and the garbage collector manages it. Per-class data such as method code and the run-time constant pool lives in the method area. How big an object is in memory is not specified; it is a choice of the JVM, and the numbers below are HotSpot's.

Why it matters: A List<Integer> of a million numbers can take several times the memory of an int[]. Knowing the layout explains why.

  1. 1One stack per thread, one shared heap

    Local variables live in the current thread's stack frame; every object and array lives on the heap that all threads share.

  2. 2References are passed by value

    A method gets a copy of the reference, so it can change the object but can't make the caller's variable point somewhere else.

  3. 3Objects carry a header

    In 64-bit HotSpot an object starts with a 12-byte header and is padded to a multiple of 8 bytes.

  4. 4Boxes cost

    An Integer is a whole object; a reference to it is 4 more bytes. Primitive arrays avoid both.

1

How it works

The explanation, with examples you can run.

Stack and heap

A local variable of a primitive type (int, double, boolean) holds the value itself in the frame. A local of a reference type holds only a reference; the object is on the heap. When the method returns, its frame is discarded at once, with no garbage collection involved. If a thread nests too many calls its stack runs out and the JVM throws StackOverflowError; if the heap can't fit a new object even after collecting, it throws OutOfMemoryError.

Java passes every argument by value. For objects, the value is the reference:

public class Refs {    static class Point { int x, y; }     static void move(Point p, int n) {        p.x = 10;          // changes the shared object on the heap        p = new Point();   // only changes this frame's copy of the reference        n = 99;            // only changes this frame's copy of the int    }     public static void main(String[] args) {        Point p = new Point();        int n = 1;        move(p, n);        System.out.println(p.x + " " + n);   // 10 1    }}

The JIT can blur the picture. If escape analysis proves an object never leaves a compiled method, HotSpot may not allocate it on the heap at all and keep its fields in registers. Your code can't tell the difference.

What an object looks like

On a 64-bit HotSpot JVM, every object starts with a header:

  • a mark word (8 bytes) used for the identity hash code, locking and the GC's age bits;
  • a class pointer (4 bytes with compressed class pointers, the default) saying what class it is.

Fields follow, arranged by the JVM rather than in source order, and the whole object is padded to a multiple of 8 bytes. Arrays add a 4-byte length. Some sizes this gives:

ValueSize
new Object()16 bytes
class Point { int x, y; }12 + 8 = 20, padded to 24
Integer16 bytes, plus a 4-byte reference to it
int[1000]16 + 4000 bytes
Integer[1000] full of distinct values16 + 4000 for the array, plus 16,000 for the boxes

References are 4 bytes because of compressed oops: HotSpot stores a reference as a 32-bit offset in units of 8 bytes from the start of the heap, which reaches about 32 GB. Set -Xmx above that and references become 8 bytes, so a 33 GB heap can hold less than a 31 GB one.

To see real layouts instead of estimates, use JOL (Java Object Layout) from OpenJDK. And watch JEP 450: in JDK 24 and later, an experimental mode shrinks the header to 8 bytes.

Allocation is cheap

HotSpot gives each thread a private chunk of the young generation, a TLAB (thread-local allocation buffer). Allocating an object is usually a pointer bump inside it, with no lock. The real cost of an object comes later, if it survives long enough for the collector to copy it.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.What does this print?
    static void move(Point p, int n) {    p.x = 10;    p = new Point();    n = 99;}// in main:Point p = new Point();   // x starts at 0int n = 1;move(p, n);System.out.println(p.x + " " + n);
  2. 2.In 64-bit HotSpot with default settings, roughly how large is an object with two int fields?
  3. 3.A deep recursion with no base case fails. With what?

0 of 3 right so far. Get all three to finish this topic.