JVM memory and garbage collection
The main JVM memory areas:
- Heap: every object. Split into a young generation (Eden and survivor spaces) and an old generation.
- Stack, one per thread: method frames, local variables and references.
- Metaspace (Java 8+, replacing PermGen): class metadata, stored in native memory.
- Code cache: native code produced by the JIT.
An object becomes garbage when no chain of references from a GC root (stack variables, static fields, active threads) reaches it. Most objects die young, so collecting the young generation often is cheap.
Collectors:
- G1: the default since Java 9, and in every environment since Java 27. Balanced.
- ZGC: sub-millisecond pauses on huge heaps; generational by default since Java 23.
- Shenandoah: low pauses; its generational mode became production-ready in Java 25.
- Parallel for maximum batch throughput, Serial for tiny heaps.
Stack, heap and metaspace
Each thread has a stack of frames holding local variables, primitives and references. Objects live on the shared heap. Class metadata lives in metaspace. Too-deep recursion overflows the stack (StackOverflowError); too many live objects fill the heap (OutOfMemoryError).
How garbage collection works
An object is garbage when nothing reachable from the GC roots (thread stacks, static fields and a few others) points to it. Most objects die young, so the heap is split into a young generation (collected often and cheaply) and an old generation (collected less often).
Choosing a garbage collector
- G1 (the default): balanced throughput and pause times for most apps.
- ZGC: very short pauses even with huge heaps; generational since Java 21 and the only mode since Java 23.
- Parallel: maximum throughput for batch jobs, with longer pauses.
- Serial: tiny heaps and single-CPU containers.
Pick one with flags such as -XX:+UseZGC.
Memory leaks in Java
Java still leaks when objects stay reachable by mistake: ever-growing static maps or caches without eviction, listeners that are never removed, ThreadLocal values in pools, and unclosed resources. Find them with a heap dump (jcmd <pid> GC.heap_dump) opened in Eclipse MAT or VisualVM.
OutOfMemoryError messages
- "Java heap space": too many live objects, or the heap is too small.
- "Metaspace": too many classes loaded (often a class-loader leak).
- "GC overhead limit exceeded": the JVM spends almost all its time collecting.
- "unable to create native thread": too many platform threads (virtual threads help).
Add -XX:+HeapDumpOnOutOfMemoryError in production so you have evidence.
Strong, soft, weak and phantom references
Normal references are strong. A SoftReference is cleared only when memory runs low (simple caches). A WeakReference is cleared at the next GC (WeakHashMap). Phantom references and Cleaner run clean-up after an object is gone, replacing finalize.
Heap sizing and containers
-Xms and -Xmx set the starting and maximum heap. In containers, the JVM reads the memory limit automatically; -XX:MaxRAMPercentage=75 is a common way to size the heap relative to it. Turn on GC logging (-Xlog:gc) before you tune anything.
Example
# Size the heap and choose a collector
java -Xms512m -Xmx2g -XX:+UseZGC -jar app.jar
# In containers, size the heap relative to the memory limit
java -XX:MaxRAMPercentage=75 -jar app.jar
# Diagnose a running JVM
jcmd <pid> GC.heap_info
jcmd <pid> Thread.print # thread dump
jcmd <pid> GC.heap_dump /tmp/heap.hprof # open in Eclipse MAT
java -XX:StartFlightRecording=duration=60s,filename=rec.jfr -jar app.jarCommon mistake
Setting -Xmx equal to the container's memory limit. Metaspace, thread stacks and direct buffers live outside the heap, so the container gets killed. Leave headroom, for example MaxRAMPercentage=75.
Under the hood
Memory leaks in Java are references you forgot about: static collections that only grow, caches with no eviction, listeners never removed, ThreadLocal values left in pooled threads. Compact object headers (production-ready in Java 25, on by default in Java 27) shrink each object header from 12 to 8 bytes on typical 64-bit JVMs, which noticeably reduces heap use for object-heavy apps.
Check yourself
What replaced PermGen in Java 8?
How this connects
Know these first
Where this leads
Part of Crack the Java interview, Upgrade from Java 8 to Java 25.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.