Java’s automatic memory management usually keeps developers from worrying about memory allocation. The garbage collector reclaims unused objects, and you keep building features. But when the heap fills up faster than the GC can clear it, or when your application hits a JVM or OS limit, you get an OutOfMemoryError. That error stops execution, crashes services, and often surfaces in production when traffic spikes or a memory leak finally exhausts available resources.
This guide walks through the exact steps to identify which OutOfMemoryError variant you are facing, diagnose the root cause, and apply the correct fix.
Prerequisites
Before working through this guide, ensure you have:
- Java Development Kit (JDK) installed (version 8 or later recommended)
- Access to your application’s JVM startup parameters
- Ability to run diagnostic tools like
jmap,jstack, or profilers (VisualVM, JProfiler, YourKit, or similar) - Write access to your application’s configuration files or deployment scripts
- Monitoring access to application logs and runtime metrics (heap usage, GC activity, thread count)
If you are running in production, you need the ability to capture heap dumps and thread dumps without restarting the service. Most APM tools, including CubeAPM, provide runtime metric dashboards that surface memory pressure before OutOfMemoryError occurs.
Step 1: Identify Which OutOfMemoryError Variant You Are Facing
Java throws different OutOfMemoryError messages depending on where the memory exhaustion occurs. The exact message tells you what limit was hit. Read the exception stack trace carefully. The variant determines the diagnosis path and the fix.
Common OutOfMemoryError variants
java.lang.OutOfMemoryError: Java heap space The heap is full. Your application tried to allocate an object, but no memory was available even after garbage collection. This is the most common variant. It often points to a memory leak, insufficient heap size, or loading too much data into memory at once.
java.lang.OutOfMemoryError: GC overhead limit exceeded The JVM spent more than 98% of its time in garbage collection across five consecutive GC cycles and reclaimed less than 2% of the heap. The application is barely making progress because the GC cannot free enough memory. This usually means the heap is too small or a memory leak is consuming almost all available space.
java.lang.OutOfMemoryError: unable to create new native thread The operating system refused to create a new thread. Your application hit the OS thread limit or ran out of native memory for thread stacks. This is not a heap issue but a threading or OS resource limit.
java.lang.OutOfMemoryError: Requested array size exceeds VM limit You tried to allocate an array larger than the JVM allows. The maximum array size is slightly less than Integer.MAX_VALUE (2^31 – 1 elements). If your heap is also too small to hold the array, this error surfaces even if the array size is within the JVM limit.
java.lang.OutOfMemoryError: Metaspace (Java 8+) Metaspace stores class metadata. If your application loads too many classes dynamically (common with heavy use of reflection, proxies, or frameworks like Spring and Hibernate), Metaspace can fill up. Before Java 8, this was called PermGen.
java.lang.OutOfMemoryError: Direct buffer memory Your application used too much off-heap memory via NIO direct buffers. This memory is outside the Java heap but still limited by JVM settings.
Check your logs or exception message to confirm which variant you hit. The rest of this guide covers diagnosis and fixes for each.
Step 2: Diagnose the Root Cause
Once you know the variant, the next step is finding why it happened. The approach differs slightly for heap issues, thread issues, and Metaspace issues.
For Java heap space errors
Capture a heap dump immediately after the error A heap dump is a snapshot of all objects in memory. Most production JVMs can be configured to generate a heap dump automatically on OutOfMemoryError by adding this flag to your JVM startup:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/heapdumps/
If the error already happened and you do not have a dump, you can manually trigger one if the process is still running:
jmap -dump:live,format=b,file=heap.hprof <pid>
Analyze the heap dump with a profiler Open the heap dump in VisualVM, Eclipse MAT (Memory Analyzer Tool), or JProfiler. Look for:
- Objects consuming the most memory (the “dominators”)
- Duplicate objects or large collections that should not be retained
- Retained objects that should have been garbage collected but are still referenced
If you see millions of instances of a single class, or a HashMap that grew unbounded, you found a memory leak. Trace back to where those objects are created and held.
Check GC logs for memory patterns Enable GC logging if not already on:
-Xlog:gc*:file=/var/log/gc.log:time,uptime,level,tags
For Java 8:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/gc.log
Look for:
- Frequent full GC cycles with little memory reclaimed
- Heap usage that climbs steadily over time without dropping after GC
- Old generation or tenured space filling up
If heap usage never drops significantly after full GC, you have a memory leak.
Inspect your code for common memory leak patterns
- Static collections that grow without bounds
- Listeners or callbacks not properly deregistered
- ThreadLocal variables not cleaned up
- Caching without eviction policies
- Long-lived objects holding references to short-lived objects
For GC overhead limit exceeded errors
This variant means the heap is nearly full and the GC is thrashing. The fix is usually the same as Java heap space errors: find the leak or increase heap size. The GC overhead limit can be disabled with:
-XX:-UseGCOverheadLimit
But disabling it only delays the inevitable OutOfMemoryError. It does not fix the underlying problem.
For unable to create new native thread errors
Check current thread count While the application is running, count threads:
jstack <pid> | grep "java.lang.Thread.State" | wc -l
Or from inside the JVM using JMX or monitoring tools.
Check OS thread limits On Linux:
ulimit -u
This shows the maximum number of processes (threads) a user can create. If your application creates thousands of threads, you will hit this limit.
Check native memory usage Each thread allocates a stack in native memory. The stack size is controlled by:
-Xss1m
If you have 5,000 threads and each has a 1 MB stack, that is 5 GB of native memory outside the heap. Reduce thread count or stack size.
Inspect thread dumps for runaway thread creation Capture a thread dump:
jstack <pid> > threads.txt
Look for:
- Hundreds of threads in WAITING or TIMED_WAITING states doing nothing useful
- Thread pools with unbounded queues creating threads on demand
- Recursive or infinite loops spawning threads
For Metaspace errors
Check Metaspace usage Use jstat to monitor Metaspace:
jstat -gc <pid> 1000
Look at the M and MU columns (Metaspace capacity and used). If Metaspace keeps growing, your application is loading new classes continuously.
Increase Metaspace size Set a larger limit:
-XX:MaxMetaspaceSize=512m
But if Metaspace grows unbounded, increasing the limit only delays the error. You likely have a classloader leak. This happens when:
- You redeploy applications in a servlet container without fully unloading the old webapp
- You use dynamic proxies or reflection heavily
- Frameworks load classes dynamically in loops
Profile classloader usage Tools like Eclipse MAT can show you how many classes are loaded and which classloaders hold them. If you see thousands of duplicate classes, you have a classloader leak.
Step 3: Apply the Fix Based on Root Cause
Once you identify the cause, apply the correct fix. Increasing heap size is often the first attempted fix, but it only works if the issue is legitimate memory demand, not a leak.
Fixing heap space issues
Increase heap size if memory demand is legitimate If your application genuinely needs more memory to handle expected load, increase the heap:
-Xmx8g
This sets the maximum heap to 8 GB. Make sure your container or VM has enough RAM to support this plus overhead for native memory, thread stacks, and OS processes.
Fix memory leaks by releasing references If the heap dump revealed a leak, update your code to release references. Common fixes:
- Clear collections when no longer needed
- Remove listeners before discarding objects
- Clear ThreadLocal variables in a
finallyblock - Implement eviction policies in caches (use a library like Caffeine or Guava)
- Use weak references (
WeakHashMap,WeakReference) for caches that can be reclaimed by GC
Optimize object retention
- Avoid loading entire datasets into memory; stream or paginate instead
- Use primitive arrays instead of boxed collections where possible (
int[]instead ofList<Integer>) - Reduce object size by removing unused fields or using compact representations
Enable CubeAPM or another APM tool to monitor heap trends OutOfMemoryError usually follows a slow buildup. CubeAPM’s runtime metrics dashboards surface heap usage, GC frequency, and object allocation rates in real time, giving you early warning before memory runs out.
Fixing GC overhead limit exceeded errors
Apply the same fixes as Java heap space errors. Additionally, tune GC settings:
Switch to a more efficient garbage collector Java 11+ defaults to G1GC, which handles large heaps better than older collectors. For very large heaps (16 GB+), consider ZGC or Shenandoah:
-XX:+UseZGC
Increase young generation size If most objects die young, a larger young generation reduces full GC frequency:
-XX:NewRatio=2
Fixing unable to create new native thread errors
Reduce thread count Use thread pools with bounded sizes. Replace code like this:
new Thread(() -> doWork()).start();
With a bounded executor:
ExecutorService executor = Executors.newFixedThreadPool(50);
executor.submit(() -> doWork());
Increase OS thread limit On Linux, increase the user process limit in /etc/security/limits.conf:
yourusername soft nproc 8192
yourusername hard nproc 16384
Or set it at runtime:
ulimit -u 8192
Reduce thread stack size Lower the stack size per thread:
-Xss512k
Only do this if your application does not have deep recursion. Smaller stacks reduce native memory usage.
Fixing Metaspace errors
Increase Metaspace limit Set a higher limit:
-XX:MaxMetaspaceSize=1g
Fix classloader leaks If Metaspace grows unbounded, audit your code for:
- Servlets or webapps that are not fully unloaded on redeploy
- Dynamic class generation in loops (e.g., using CGLIB, Javassist, or ASM)
- Third-party libraries that generate classes at runtime
Most application servers (Tomcat, Jetty, WildFly) have known classloader leak patterns. Search for your server name plus “classloader leak” for mitigation steps.
Step 4: Validate the Fix in a Test Environment
Never apply JVM tuning changes directly to production without testing. Deploy the fix to a staging environment that mirrors production load.
Run load tests with realistic traffic Simulate peak traffic using tools like JMeter, Gatling, or Locust. Monitor:
- Heap usage over time
- GC frequency and pause times
- Thread count stability
- Application throughput and latency
If heap usage stays stable and GC pauses are acceptable, the fix works. If heap still climbs or errors reappear, repeat the diagnosis.
Monitor in production after deployment Deploy to a small production subset (canary deployment) first. Watch heap trends, GC logs, and error rates. If stable, roll out fully.
Step 5: Add Monitoring to Detect Future Issues Early
OutOfMemoryError is often preventable with the right monitoring. Most memory issues build up slowly over hours or days before the final crash.
Set up heap usage alerts Configure alerts when heap usage crosses 80% or 90%. This gives you time to investigate before the error occurs. Tools like CubeAPM provide built-in alerting on JVM metrics including heap usage, GC pause time, and thread count.
Track GC metrics continuously Monitor:
- GC pause time (high pauses degrade user experience)
- GC frequency (too many full GCs indicate memory pressure)
- Heap usage after GC (if it does not drop, you have a leak)
Correlate memory issues with deployments If OutOfMemoryError starts after a code deploy, the new code likely introduced a leak. APM tools with deployment tracking make this correlation automatic.
Enable heap dumps on OutOfMemoryError in production Add this to every production JVM:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/heapdumps/
If an error occurs, you have the heap dump for immediate analysis instead of waiting for a reproduction.
Troubleshooting Common Issues
Heap dump file is too large to analyze Heap dumps can be tens of gigabytes for large applications. Use MAT with increased memory:
./MemoryAnalyzer -vmargs -Xmx16g -jar heap.hprof
Or analyze remotely on a machine with more RAM.
OutOfMemoryError happens intermittently and cannot be reproduced This usually means a race condition or a load-dependent issue. Capture multiple heap dumps over time and compare object counts. Look for objects that accumulate only under certain conditions (e.g., high traffic, specific user actions).
Increasing heap size delays the error but does not fix it You have a memory leak. Increasing heap only buys time. Follow the heap dump analysis steps to find and fix the leak.
GC logs show frequent full GCs but heap usage is low Your application may be creating and discarding many short-lived objects. Tune the young generation size or switch to a more efficient GC algorithm.
Thread count keeps growing even with thread pools Check for thread pools with unbounded queues or incorrect configuration. Ensure pools have maximum size limits and rejection policies.
Conclusion
OutOfMemoryError is not a single problem but a family of issues with distinct causes and fixes. The Java heap space variant points to memory leaks or undersized heaps. The GC overhead limit exceeded variant signals extreme memory pressure. The unable to create new native thread variant indicates threading or OS limits. Metaspace errors come from classloader leaks or excessive dynamic class generation.
The correct fix depends on accurate diagnosis. Capture heap dumps, analyze GC logs, and inspect thread usage before making changes. Increasing heap size works only if memory demand is legitimate. If you have a leak, you must fix the code. If you hit OS limits, you must reduce thread count or raise limits.
Monitoring tools that surface JVM metrics in real time prevent most OutOfMemoryError incidents by giving early warning. CubeAPM provides runtime metrics dashboards that track heap usage, GC activity, and thread count, with alerts that fire before memory runs out.
Disclaimer: The information in this article reflects the latest details available at the time of publication and may change as technologies and products evolve. Features, pricing, and JVM behavior can change across Java versions. Always verify the latest information directly with the official Java documentation and your JVM vendor before making production changes.
Frequently Asked Questions
What causes OutOfMemoryError in Java?
OutOfMemoryError happens when the JVM cannot allocate memory for a new object, thread, or class. Common causes include memory leaks, insufficient heap size, hitting OS thread limits, or exhausting Metaspace with too many dynamically loaded classes.
How do I fix Java heap space error?
Capture a heap dump with jmap, analyze it in a profiler like Eclipse MAT, and identify which objects are consuming memory. If you have a memory leak, fix the code to release references. If memory demand is legitimate, increase heap size with the Xmx flag.
What is the difference between heap space and Metaspace errors?
Java heap space errors occur when the heap used for application objects is full. Metaspace errors occur when the area storing class metadata is full. Heap errors usually point to object leaks. Metaspace errors point to classloader leaks or excessive dynamic class generation.
How do I prevent OutOfMemoryError in production?
Set up monitoring for heap usage, GC frequency, and thread count with alerts at 80% or 90% thresholds. Enable heap dumps on OutOfMemoryError with the HeapDumpOnOutOfMemoryError JVM flag. Use APM tools like CubeAPM to track memory trends over time.
Why does increasing heap size not fix the error?
If increasing heap size only delays the error, you have a memory leak. The application will eventually fill the larger heap. Use heap dump analysis to find and fix the leak instead of repeatedly increasing heap size.
What does GC overhead limit exceeded mean?
It means the JVM spent more than 98% of its time in garbage collection over five consecutive cycles and reclaimed less than 2% of the heap. The application is making almost no progress. The fix is usually the same as Java heap space errors: find the leak or increase heap size.
How do I analyze a heap dump?
Open the heap dump in Eclipse MAT, VisualVM, or JProfiler. Look at the dominator tree to find which objects consume the most memory. Check for duplicate objects, unbounded collections, or objects that should have been garbage collected but are still retained.





