What does 'OutOfMemoryError: Java heap space' mean, what are its common causes, and what is your first remediation step?
answer
- Heap full of still-reachable objects + GC can't help
- Leak climbs / under-size is high-but-flat / spike is one big op
- First step = heap dump, not -Xmx
- Dominator tree + retained size in MAT/VisualVM
- Bumping -Xmx masks leaks, lengthens GC pauses
basics
~20 sIt means the heap — the area where Java objects live — is full and the garbage collector can't free enough room for a new object. Common causes are a memory leak, a heap that's simply too small for the workload, or trying to load too much data at once. First step: capture a heap dump and check whether it's a leak or just under-sizing.
solid answer
~50 s'Java heap space' is the most common OOME variant: the heap (where all `new` objects live) is exhausted and the GC cannot reclaim enough to satisfy an allocation. Three families of causes: (1) a genuine **leak** — objects stay reachable from a GC root (static collections, caches, unremoved listeners, ThreadLocals in pooled threads) so they're never collected and retained heap grows without bound; (2) **under-sizing** — the workload legitimately needs more than `-Xmx` allows; (3) a **runaway/spike allocation** — loading a huge result set or file fully into memory. First remediation step is *diagnose, don't blindly bump -Xmx*: enable `-XX:+HeapDumpOnOutOfMemoryError`, capture the dump, and open it in Eclipse MAT or VisualVM to inspect the dominator tree and retained sizes. If one structure holds most of the heap and keeps growing across time, it's a leak — fix the reachability. If usage is legitimately flat-but-high, raise `-Xmx`. Bumping the heap on a real leak only delays the crash.
code
java · 13 lines// Classic logical leak: an unbounded static cache. Entries are
// added but never evicted, so they stay reachable forever and
// the heap climbs until 'OutOfMemoryError: Java heap space'.
public final class Registry {
private static final Map<String, byte[]> CACHE = new HashMap<>();
public static void remember(String id) {
CACHE.put(id, new byte[1_000_000]); // never removed
}
}
// Fix: bound it (e.g. an LRU / size cap) or use weak keys,
// so old entries become unreachable and the GC can reclaim them.go deeper
Knows the heap holds objects, that this OOME means the heap is full, and that a leak or a too-small heap can cause it.
Distinguishes leak vs. under-size vs. spike, names common leak patterns, and knows the first step is a heap dump rather than blindly raising -Xmx.
Drives a dump-based investigation (dominator tree, retained size, comparing dumps over time), articulates why bumping -Xmx masks leaks and affects GC pauses, and ties spikes to streaming/pagination fixes.
Establishes standards: HeapDumpOnOutOfMemoryError everywhere, container-aware -Xmx (MaxRAMPercentage), capacity planning vs. leak triage, and guardrails (bounded caches, eviction) so under-sizing and leaks are caught before prod.
## The heap Every object you create with `new` — strings, lists, your domain objects — lives on the **heap**, a region of memory the JVM reserves for object storage. Its maximum size is set by the **`-Xmx`** flag (e.g. `-Xmx512m` = 512 MB max). The **garbage collector (GC)** periodically frees heap objects that are no longer **reachable** — meaning no chain of references from a **GC root** (a live thread's local variables/stack, static fields, etc.) can reach them anymore. Unreachable = garbage = collectable. ## What the error means `OutOfMemoryError: Java heap space` is thrown when: 1. Your program asks to allocate an object on the heap, **and** 2. There isn't enough free heap, **and** 3. The GC has run and **still** can't reclaim enough. So it's not 'the heap is momentarily busy' — it's 'the heap is full of objects that are all still reachable (or the request is bigger than the whole heap)'. ## The three cause families **1. Memory leak (logical leak).** In Java a 'leak' doesn't mean the GC is broken — it means objects you *thought* were done with are still **reachable**, so the GC correctly refuses to free them. Retained heap climbs steadily over time. Classic patterns: - An ever-growing `static` collection or unbounded cache that you add to but never evict. - Listeners/callbacks registered but never deregistered. - `ThreadLocal` values never removed in a thread *pool* (the thread lives forever, so the value does too). - Keys whose `hashCode`/`equals` make them un-removable. Signature: a slow, steady climb in used heap; the app survives a while then dies; usually the *same* structure dominates the heap. **2. Under-sizing.** The application's legitimate working set simply exceeds `-Xmx`. Usage is high but **stable** (it doesn't keep climbing forever) — it just doesn't fit. Common when a process is given a tiny heap relative to its real workload, or in a container where `-Xmx` wasn't aligned with the container memory limit. **3. Runaway / spike allocation.** A single operation tries to materialize an enormous amount at once: `SELECT * FROM huge_table` into a `List`, reading a multi-GB file fully into a `byte[]`, or building a giant string. Usage is normally fine, then one request spikes past the ceiling. ## First remediation step: diagnose before resizing The instinctive fix — 'just add `-Xmx`' — is only correct for **under-sizing**. On a **leak** it merely postpones the crash. So: 1. Run with **`-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path`** so the JVM writes a heap dump (.hprof) at the moment of failure. (You can also grab one on demand with `jmap` / `jcmd`.) 2. Open the dump in **Eclipse MAT** or **VisualVM**. Look at: - **Retained size** — how much heap would be freed if a given object went away. - **Dominator tree** — which few objects 'hold down' most of the heap. 3. Interpret: - One structure with huge retained size that grows across successive dumps → **leak**; fix the reachability (evict from the cache, deregister the listener, `remove()` the ThreadLocal). - Heap legitimately full of needed objects, flat over time → **under-size**; raise `-Xmx` (and check the container limit). - A spike tied to one operation → **stream/paginate** instead of loading everything (process in chunks, use a cursor, set a fetch size). ## Why not just bump -Xmx reflexively A bigger heap on a real leak buys time, not a fix — and a larger heap can mean **longer GC pauses**. Worse, hiding the leak lets it grow until it crashes again at a less convenient time. Diagnose first; resize only when the data says the working set is genuinely too big. ## Key takeaways - Heap space OOME = heap full of *reachable* objects (or one over-large request) and GC can't help. - Three causes: leak (climbs), under-size (high but flat), spike (one big operation). - First step: heap dump → dominator tree / retained size → decide leak vs. size vs. stream. - Don't reflexively raise -Xmx; it masks leaks and lengthens GC pauses.
- You raised -Xmx and the crash moved from 2 hours to 4 hours but still happens. What does that tell you?It's almost certainly a leak, not under-sizing. A bigger heap just took longer to fill. Stop resizing and take a heap dump to find the structure with growing retained size.
- How do you distinguish a leak from legitimate high usage from a single heap dump?One dump is weak evidence; compare dumps over time (or watch used-heap-after-full-GC). A leak shows the same dominator's retained size growing across time; legitimate usage stays flat after GC.
Think of the heap as a parking garage. 'Heap space' OOME is a full garage where every car still belongs to someone (reachable) — a leak is when cars never leave, under-sizing is a garage that's simply too small for the city, and a spike is one truck convoy trying to park all at once.
saying these in an interview costs you the question
- Saying the fix is always to increase -Xmx (only fixes under-sizing; masks leaks).
- Believing a Java 'leak' means the GC is broken — it means objects are still reachable.
- Assuming the OOME stack trace identifies the leak (it's just where the heap finally overflowed).
- Confusing heap-space OOME with Metaspace OOME (classes) or stack overflow (thread stack).