Java: java.lang.OutOfMemoryError: Java heap space
Java heap space out of memory
Verified against JDK 21 docs (troubleshooting), Eclipse MAT guide, Stack Overflow #37335/java-heap-space · Updated April 2026
> quick_fix
The JVM hit its -Xmx (max heap) limit. Either your app genuinely needs more memory (raise -Xmx), or you have a memory leak (objects retained beyond their useful life). Capture a heap dump to tell the difference.
# Immediate workaround: raise heap to 4GB
java -Xmx4g -jar myapp.jar
# Better: enable heap dump on OOM
java -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap.hprof -jar myapp.jar
# Analyse heap.hprof in Eclipse MAT or jvisualvmWhat causes this error
The JVM pre-allocates a fixed-size heap (controlled by -Xmx). When live objects plus unreachable-but-not-yet-collected objects fill the heap, the garbage collector runs. If even after GC there's not enough space to allocate the requested object, the JVM throws OutOfMemoryError: Java heap space and the app dies.
How to fix it
- 01
step 1
Raise -Xmx temporarily to confirm it's a sizing issue
If the app runs fine at 4GB but crashes at 2GB, you probably just need a bigger heap. If it crashes at any -Xmx you give it, you have a leak.
- 02
step 2
Enable automatic heap dump on OOM
Add -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap.hprof. When the next OOM happens, you'll have a snapshot to analyse.
- 03
step 3
Analyse the heap dump
Open heap.hprof in Eclipse Memory Analyzer (MAT). The Leak Suspects report tells you which class is holding the most memory and why.
- 04
step 4
Common leak patterns
Static collections that grow forever (HashMap<String, Something> caches). Thread locals not cleared. Stream+collect piping huge files into memory. Listener lists not unregistered.
- 05
step 5
For genuine size needs, tune the GC
Use G1GC or ZGC for heaps > 4GB. G1GC is default on JDK 9+; ZGC is pauseless and scales to TBs.
# ZGC for low-pause, large-heap apps java -XX:+UseZGC -Xmx16g -jar myapp.jar
Why OutOfMemoryError happens at the runtime level
The JVM allocates a fixed-size heap split into Young (Eden + two Survivor spaces) and Old generations. The garbage collector promotes long-lived objects from Young to Old. When an allocation request can't be satisfied even after a full GC, the JVM throws java.lang.OutOfMemoryError: Java heap space from the allocation site in the runtime. Internally, this is the GC's giveup signal: the live set fills more than the configured -Xmx and the collector has nothing more to reclaim. Heap dump triggers (HeapDumpOnOutOfMemoryError) capture the entire heap to disk so you can identify the dominator tree of retained objects.
Common debug mistakes for OutOfMemoryError
- Doubling -Xmx without checking the host's available physical memory, the JVM allocates virtual memory aggressively, and on a container with a 4GB limit, -Xmx8g ends in a kernel OOM kill long before the JVM throws.
- Reading the stack trace where OOM was thrown and fixing the immediate allocation, that line is the unlucky victim, not the leak source; the actual leak is upstream and may not appear in the trace at all.
- Switching from the default G1GC to CMS for 'faster' collection, CMS was deprecated in JDK 9 and removed in 14, and reverting to it on a modern JDK makes the leak worse, not better.
- Adding System.gc() calls in code expecting to force cleanup, System.gc() is advisory; G1GC and ZGC ignore it under most policies, and the leak is from references the GC can already see, so manual triggering can't help.
- Profiling with jvisualvm attached to production, the agent itself adds 200-500MB of overhead and can push a marginal app over the OOM boundary, giving false confidence when you remove the agent.
When OutOfMemoryError signals a deeper problem
OutOfMemoryError that returns weekly under load is a sign the application has unbounded caches or unbounded queues, patterns the Java ecosystem encourages but the JVM can't auto-correct. Common architectural causes: HashMap caches with no eviction policy, BlockingQueue with default Integer.MAX_VALUE capacity buffering producer overflow, ThreadLocal in a thread pool that recycles threads but never clears the locals, listener lists where unregister was forgotten in a service-shutdown path. The fix is to pick bounded data structures by default (Caffeine, Guava cache with maxSize, LinkedBlockingQueue with explicit capacity) and let the load shedder fail fast rather than the GC fail late.
Editor's take
This error has a particular talent for appearing at 2am on a Wednesday, three hours before a scheduled database migration in a monolith that nobody's touched in eighteen months. The deployment pipeline finished clean, staging looked fine at 200 concurrent users, but prod runs 2,000 — and the JVM was configured with -Xmx512m because whoever wrote the Dockerfile in 2019 copied it from a tutorial. The platform team lead gets paged, realizes heap dumps are disabled in the prod JVM flags (missing -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app/), and now they're debugging blind against a process that already crashed and recycled.
Knowing how to actually diagnose this error — rather than just bumping -Xmx and hoping — is a reliable signal of mid-career Java competence. A junior engineer raises the heap and calls it fixed. The engineer who's been around will immediately ask: is this a leak or genuine growth? They'll reach for Eclipse MAT or VisualVM, look for retained object histograms, and know what a dominator tree is. Understanding GC generation promotion, soft reference behavior under memory pressure, and the difference between -Xmx and -XX:MaxMetaspaceSize (a separate OOM vector entirely) separates engineers who've read about the JVM from those who've operated it.
In practice, this error rarely arrives alone. Expect java.lang.OutOfMemoryError: GC overhead limit exceeded to appear in adjacent threads if the GC is spending more than 98% of CPU trying to reclaim space — that one surfaces earlier and is often the first warning you missed. Downstream, watch for SocketTimeoutException and java.net.ConnectException as dependent services time out against a starved JVM. If you're on Spring Boot, a heap-exhausted app will stop responding to /actuator/health before it dies, which trips circuit breakers and cascades to callers before the OOM itself is logged.
By Bikram Nath · Curator · Updated April 2026
Frequently asked questions
What's the difference between OutOfMemoryError: Java heap space and Metaspace?
Heap space = your objects (String, MyClass instances). Metaspace = class metadata (class definitions, method bytecode). Different memory regions, different fixes. This page covers heap space only.
Does adding memory always fix OutOfMemoryError?
No. If you have a leak, adding memory just delays the crash. Leak must be found in the heap dump.
What heap size should I give my app?
Start with -Xmx2g for typical web apps. Monitor peak usage via JMX. Set -Xmx to peak + 30% headroom.