JVM Internals & Memory
Memory Layout: Heap, Stack & Object Anatomy
- Object = header (mark word + class pointer) + fields (padded to 8-byte alignment)
- Compressed oops: 4-byte references below ~32 GB heap — crossing that threshold costs memory
- An
Integerweighs ~16 bytes vs 4 for anint;int[]vsInteger[]is night and day - Escape analysis lets the JIT stack-allocate or scalarize objects that don't escape
- The heap is generational: young (eden + survivors) and old — allocation happens in TLABs
- Native memory (direct buffers, metaspace, thread stacks) is invisible to heap dashboards
class Point { int x; int y; } // 12-byte header + 4 + 4 = 20 → padded to 24 bytes
new Point[1_000_000] // 4 MB of refs + 24 MB of Points — scattered on the heap
int[] xs; int[] ys; // SoA layout: 8 MB total, contiguous, cache-streamable
// java.lang.Integer: 12-byte header + 4-byte int = 16 bytes → 4× an int, plus indirectionThe mark word multiplexes identity hash, lock state, and GC age bits — why System.identityHashCode, synchronized, and object aging all touch the same header. The class pointer feeds getClass() and virtual dispatch. Arrays add a 4-byte length. Use JOL (jol-core) to print real layouts — field ordering is JVM-chosen (it packs and reorders to minimize padding).
Allocation is nearly free: each thread bump-allocates in its private TLAB (thread-local allocation buffer) — pointer increment, ~10 instructions, no lock. This plus generational GC (Gc Fundamentals) is why "avoid allocating" is not a default rule in Java, only a hot-path rule (Language Performance). Large objects (typically big arrays) may go straight to the old generation or special regions (G1 "humongous").