What types of OutOfMemoryError exist
An OutOfMemoryError (OOME) in Java is not a single generic exception; it is a family of runtime errors where the JVM's error message explicitly identifies which specific memory...
🟢 Junior Level
An OutOfMemoryError (OOME) in Java is not a single generic exception; it is a family of runtime errors where the JVM’s error message explicitly identifies which specific memory subsystem or operating system boundary was exhausted.
Real-World Analogy
Think of a busy restaurant kitchen running out of resources:
Java heap space: The kitchen counters are completely covered in dirty dishes; no more plates can be prepped.Metaspace: The chef’s recipe binder is out of pages; no new recipes or menu items can be written down.Direct buffer memory: The walk-in refrigerator leased from the landlord next door (Off-Heap Native Memory) is full, even though the main kitchen counters are empty.unable to create new native thread: The city fire marshal limits the building to 50 staff members; trying to hire line cook #51 gets the restaurant shut down (OS Thread Limit).
Memory Subsystems & Their Associated OOMEs
┌────────────────────────────────────────────────────────────────────────┐
│ JVM MEMORY SUBSYSTEMS │
├────────────────────────────────────────────────────────────────────────┤
│ 1. JAVA HEAP (-Xmx) │
│ • `Java heap space` (Heap full of unreachable/retained objects) │
│ • `GC overhead limit exceeded` (GC spends >98% CPU freeing <2% RAM) │
│ • `Requested array size exceeds VM limit` (Array > Integer.MAX_VAL) │
├────────────────────────────────────────────────────────────────────────┤
│ 2. METASPACE & CLASS METADATA (Native Off-Heap) │
│ • `Metaspace` (Class metadata exceeds -XX:MaxMetaspaceSize) │
│ • `Compressed class space` (Exceeds -XX:CompressedClassSpaceSize) │
├────────────────────────────────────────────────────────────────────────┤
│ 3. OPERATING SYSTEM / NATIVE MEMORY (Off-Heap) │
│ • `Direct buffer memory` (NIO/Netty exceeds -XX:MaxDirectMemorySize)│
│ • `unable to create new native thread` (OS ulimit / RAM exhausted) │
│ • `request <size> bytes for <reason>. Out of swap space?` (malloc) │
│ • `Map failed` (Linux vm.max_map_count limit reached) │
└────────────────────────────────────────────────────────────────────────┘
🟡 Middle Level
The 5 Most Frequent Production OOMEs: Root Causes & Solutions
1. java.lang.OutOfMemoryError: Java heap space
- Root Cause: The volume of live objects reachable from active GC Roots exceeds the maximum configured heap limit (
-Xmx). - Common Triggers: Unbounded static collections, unevicted in-memory caches, memory leaks in session handlers, or an undersized heap under sudden traffic bursts.
- Remediation: Capture an automatic heap dump (
-XX:+HeapDumpOnOutOfMemoryError) and inspect the Dominator Tree in Eclipse MAT to locate the root memory accumulator.
2. java.lang.OutOfMemoryError: GC overhead limit exceeded
- Root Cause: A built-in HotSpot safety heuristic that triggers when the JVM spends $> 98\%$ of total CPU time executing GC pauses while reclaiming $< 2\%$ of heap space.
- Why it Exists: Prevents the application from locking up indefinitely in a near-permanent Stop-The-World loop with zero throughput.
- Remediation: Do not disable the heuristic via
-XX:-UseGCOverheadLimit! Fix the underlying heap leak or increase heap headroom (-Xmx).
3. java.lang.OutOfMemoryError: Metaspace vs. Compressed class space
Metaspace: Loaded class metadata, method bytecodes, constant pools, and annotations exceed-XX:MaxMetaspaceSize.- Trigger: ClassLoader leaks during dynamic application redeployments or un-cached runtime Groovy/JavaScript compilations.
Compressed class space: Exceeds the 1 GB dedicated region reserved for 32-bitInstanceKlasspointers (-XX:CompressedClassSpaceSize=1g).- Trigger: Generating millions of dynamic proxy classes (CGLIB, ByteBuddy, Spring AOP) without caching.
4. java.lang.OutOfMemoryError: Direct buffer memory
- Root Cause: Off-heap memory allocated via
ByteBuffer.allocateDirect()(frequently used by Netty, gRPC, and Kafka) exceeds-XX:MaxDirectMemorySize(which defaults to-Xmx). - The Paradox: Can occur even when the Java Heap is 95% empty!
- Remediation: Ensure byte buffers are returned to buffer pools (
ReferenceCountUtil.release(buf)) and track allocations using Native Memory Tracking (-XX:NativeMemoryTracking=detail).
5. java.lang.OutOfMemoryError: unable to create new native thread
- Root Cause: The JVM attempted to spawn a new platform thread (
new Thread()), but the host operating system refused the allocation. - The Culprits:
- The OS kernel process thread limit was breached (Linux
ulimit -uor/proc/sys/kernel/pid_max). - Physical RAM is exhausted by thread stacks ($N_{\text{threads}} \times \text{-Xss1m}$).
- The OS kernel process thread limit was breached (Linux
- Remediation: Check
ulimit -u, eliminate unbounded thread pools, reduce stack size (-Xss512k), or migrate blocking I/O to Java 21 Virtual Threads.
🔴 Senior Level
Advanced & Edge-Case OOME Types
1. Requested array size exceeds VM limit
// Throws: java.lang.OutOfMemoryError: Requested array size exceeds VM limit
int[] massiveArray = new int[Integer.MAX_VALUE];
- Why it Happens: In the HotSpot JVM, an array object header stores its length in a 32-bit signed integer. However, the JVM reserves a few header words for internal metadata (object header, mark word, klass pointer).
- Consequently, the maximum allowable array size is between
Integer.MAX_VALUE - 2andInteger.MAX_VALUE - 8(typically $2{,}147{,}483{,}639$ elements), regardless of available RAM.
2. request <size> bytes for <reason>. Out of swap space?
- Mechanism: The JVM’s internal C++ runtime attempted to allocate native memory via
os::malloc()and received aNULLreturn pointer. - Indication: The host operating system has run completely out of physical RAM and swap space.
- Output: The JVM immediately aborts execution and generates a fatal native crash log (
hs_err_pid.log).
3. Map failed
- Mechanism: Occurs when using memory-mapped I/O (
FileChannel.map()/MappedByteBuffer) and the operating system rejects themmap()system call. - Linux Cause: The process breached the maximum virtual memory map count enforced by the Linux kernel (
sysctl vm.max_map_count, default 65,530). Common in Elasticsearch, Kafka, and Lucene databases. - Remediation: Enlarge kernel mapping limits:
sysctl -w vm.max_map_count=262144.
Diagnostic Decision Matrix
| Error Detail Message | First Diagnostic Command | Primary JVM / OS Tuning Flags |
|---|---|---|
Java heap space |
Open .hprof dump in Eclipse MAT |
-Xmx, -XX:MaxRAMPercentage |
GC overhead limit exceeded |
Inspect GC logs (-Xlog:gc*) for thrashing |
Profile object churn, increase -Xmx |
Metaspace |
jcmd <PID> VM.metaspace show-loaders |
-XX:MaxMetaspaceSize=512m |
Compressed class space |
Inspect proxy generation in classloaders | -XX:CompressedClassSpaceSize=2g |
Direct buffer memory |
jcmd <PID> VM.native_memory detail |
-XX:MaxDirectMemorySize=2g |
unable to create native thread |
Check ulimit -u and jstack thread counts |
-Xss512k, use Virtual Threads (Java 21) |
Map failed |
Check cat /proc/sys/vm/max_map_count |
sysctl -w vm.max_map_count=262144 |
4 Tricky Questions
1. Why does increasing -Xmx actually make OutOfMemoryError: unable to create new native thread worse rather than better?
Answer: The unable to create new native thread error occurs when the operating system refuses to allocate memory for a new OS thread stack (each platform thread allocates a dedicated 1 MB stack via -Xss1m from native OS RAM, completely outside the Java Heap). In a 64-bit container with a fixed memory ceiling (e.g., 4 GB), expanding -Xmx from 2 GB to 3.5 GB forces the JVM to claim almost all available physical RAM for the Heap. This leaves a tiny sliver of only 500 MB for thread stacks, Metaspace, and native code. With less native RAM remaining, the operating system runs out of memory faster, reducing the maximum number of threads the application can spawn and accelerating the crash. The proper fix is often to decrease -Xmx or migrate to Java 21 Virtual Threads.
2. What is the fundamental difference between OutOfMemoryError: Metaspace and OutOfMemoryError: Compressed class space?
Answer: Metaspace is the broad native memory area storing all class metadata (method bytecodes, runtime constant pools, annotations, and virtual method dispatch tables). Within Metaspace, the JVM reserves a specific, contiguous 1 GB memory block called the Compressed Class Space (-XX:CompressedClassSpaceSize=1g) when Compressed Class Pointers are enabled (-XX:+UseCompressedClassPointers). This 1 GB region stores only the C++ InstanceKlass structures so they can be referenced using 32-bit pointer offsets from object headers. An application can throw Compressed class space OOM if it generates hundreds of thousands of dynamic proxy classes, even if general Metaspace has plenty of headroom and the host OS has gigabytes of free RAM.
3. What does the JVM flag -XX:-UseGCOverheadLimit do, and why is disabling it considered an anti-pattern?
Answer: By default, HotSpot enables -XX:+UseGCOverheadLimit, a safety heuristic that throws an OutOfMemoryError if the JVM spends more than 98% of its CPU time executing Garbage Collection while recovering less than 2% of heap memory. Disabling this flag via -XX:-UseGCOverheadLimit simply turns off this safety check. It does not solve the memory problem; instead, it allows the JVM to enter a state of total CPU thrashing: all CPU cores run at 100% execution capacity executing back-to-back Stop-The-World Full GCs, application throughput drops to zero, and the application freezes indefinitely until it finally crashes minutes later with OutOfMemoryError: Java heap space.
4. How can OutOfMemoryError: Direct buffer memory occur when the Java Heap is 95% empty?
Answer: Direct memory is allocated outside the Java Heap in native operating system memory via ByteBuffer.allocateDirect() (using native C malloc()). On the Java Heap, a DirectByteBuffer is represented only by a tiny Java wrapper object occupying roughly 64 bytes. The deallocation of the underlying native memory is handled by a Cleaner (based on PhantomReference) that executes only when the 64-byte wrapper object is garbage collected. If the application performs heavy network I/O (allocating gigabytes of native direct buffers) but creates very few on-heap objects, the Java Heap never fills up. Because the heap is 95% empty, the Garbage Collector never triggers, the Java wrapper objects are never collected, and the underlying native direct memory grows unchecked until it breaches -XX:MaxDirectMemorySize.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“An
OutOfMemoryErrorindicates exhaustion of a specific JVM memory subsystem, not just the Heap. WhileJava heap spacesignals that live objects exceed-Xmx,GC overhead limit exceededwarns that the collector is thrashing (>98% CPU spent freeing <2% memory). In native memory,MetaspaceandCompressed class spaceoccur when dynamic bytecode generation exhausts class metadata areas.Direct buffer memorystrikes when off-heap NIO buffers exceed-XX:MaxDirectMemorySize(even on an empty heap). Finally,unable to create new native threadoccurs when OS thread limits or native stack memory are exhausted; counter-intuitively, increasing-Xmxworsens this error by starving native RAM.”
Key Architectural Takeaways
- Specific Error Messages: Never treat OOMEs generically; the detail message pinpoints the exact exhausted subsystem.
- Heap vs. Native:
Java heap spaceandGC overheadoccur in the Heap;Metaspace,Direct buffer, andnative threadoccur in native OS RAM. GC overhead limit: A safety valve preventing infinite GC loops; never disable it with-XX:-UseGCOverheadLimit.- Thread Creation Paradox: Increasing
-Xmxreduces native RAM available for thread stacks, worseningunable to create new native thread. - Array Size Limit: Arrays cannot exceed
Integer.MAX_VALUE - 2due to 32-bit length header constraints. - Direct Buffer Decoupling: Direct buffers reside off-heap; they can exhaust native memory even when the heap is nearly empty.
Red Flags & Anti-Patterns
- 🚩 Assuming every OOME is solved by increasing
-Xmx: Increasing-Xmxaccelerates native thread and direct buffer crashes. - 🚩 Disabling
-XX:-UseGCOverheadLimitto “fix” an error: Simply trades a fast failure for a total CPU freeze followed by a crash. - 🚩 Confusing
MetaspacewithCompressed class space: Fails to recognize the 1 GB ceiling reserved for 32-bit Klass pointers. - 🚩 Confusing
StackOverflowErrorwithunable to create new native thread: StackOverflow is deep recursion on one thread; native thread OOM is failure to spawn a new OS thread. - 🚩 Ignoring Linux kernel limits (
vm.max_map_count): Fails to diagnoseMap failederrors in database and messaging workloads.
Related Topics
- What happens on OutOfMemoryError — The OOM escalation ladder and Zombie State
- What is Metaspace (or PermGen) — Class metadata storage and ClassLoader leaks
- What are -Xms and -Xmx parameters — Heap sizing limits and container RSS
- What is a heap dump — Capturing and analyzing HPROF memory snapshots
- What is a memory leak in Java — Unintentional object retention patterns