OutOfMemoryError types in Java
Java heap space, Metaspace, GC overhead limit, unable to create native thread.
Read the message first
OutOfMemoryError tells you which memory ran out. Java heap space: live objects filled the heap. Metaspace: too many classes. GC overhead limit exceeded: the JVM spends almost all its time collecting and frees almost nothing. unable to create native thread: the OS refused another thread.
Classes die with their loader
A class can be unloaded only when its whole defining class loader is unreachable. Every hot redeploy creates a new loader with fresh copies of every class. If anything still references the old loader (a static, a ThreadLocal, a JDBC driver registry), all its classes stay in metaspace.
Just add heap?
Production throws "OutOfMemoryError: unable to create native thread". Will raising -Xmx from 4g to 8g fix it?
Yes, threads live in the heapNo: threads need OS resources and native memory
Show the answer
No. Platform threads and their stacks use native memory and OS limits, outside the heap. A bigger heap can even leave less native memory. Cap threads with a pool, or use virtual threads.
What kind of Throwable?
What does this print?
Throwable t = new OutOfMemoryError("Metaspace");
System.out.println(t instanceof Exception);
System.out.println(t instanceof Error);
System.out.println(t.getMessage());false true Metaspacetrue false Metaspacetrue true null
Show the answer
OutOfMemoryError extends **Error**, not Exception, so it's unchecked: no method has to declare it. The message string names the exhausted area.
Catch it and carry on?
Catching OutOfMemoryError to keep going is a trap: it can hit any thread at any allocation, leaving half-updated state behind. It signals a serious JVM problem. Find the cause; don't hide it.
try {
process(batch);
} catch (OutOfMemoryError e) {
// "just retry"? state may be corrupt
}Capture evidence automatically
Run production with **-XX:+HeapDumpOnOutOfMemoryError** (and -XX:HeapDumpPath). The JVM writes an .hprof heap dump at the moment of the crash, so you can see which objects filled the heap and what kept them alive, instead of guessing after a restart.
Key takeaways
- Read the message: it names the exhausted area
- Metaspace OOM often means a class loader leak
- Native thread OOM is fixed with fewer threads, not more heap
- -XX:+HeapDumpOnOutOfMemoryError captures evidence
With the Parallel collector, "GC overhead limit exceeded" is thrown when more than 98% of the time goes to GC while less than 2% of the heap is recovered.
Practice questions
After the 20th hot redeploy on an app server, the app dies with OutOfMemoryError: Metaspace. Most likely cause?
- Too many threads were started
- Strings are interned in metaspace
- The heap (-Xmx) is too small
- Old class loaders are still referenced, so their classes are never unloaded
Check your answer
Old class loaders are still referenced, so their classes are never unloaded. Each redeploy creates a new class loader with fresh copies of every class. If anything (a static, a ThreadLocal, a JDBC driver registry) still references the old loader, all its classes stay in metaspace.
Production throws "OutOfMemoryError: unable to create native thread". A teammate suggests raising -Xmx from 4g to 8g. Good idea?
- No: thread stacks use native memory and OS limits; cap threads with a pool or use virtual threads
- No: you should lower -Xss to 0
- Yes, threads are allocated in the heap
- Yes, a bigger heap always fixes OOM
Check your answer
No: thread stacks use native memory and OS limits; cap threads with a pool or use virtual threads. Platform threads need OS resources and native stack memory outside the heap. A bigger heap can even leave less native memory, so the real fix is fewer platform threads.