Java is a robust and memory-managed programming language, but like any managed environment, it’s still susceptible to memory issues. One of the most dreaded runtime problems in a Java application is the java.lang.OutOfMemory
error. This error is thrown when the Java Virtual Machine (JVM) cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.
This article walks through the common causes of OutOfMemoryError
, how to detect and diagnose them, and most importantly, how to fix them with practical coding examples and memory management strategies.
Understanding What Causes OutOfMemoryError
There are several types of OutOfMemoryError
s in Java, including:
-
Java heap space
-
GC overhead limit exceeded
-
Metaspace
-
Direct buffer memory
-
Unable to create new native thread
Let’s break these down one by one:
1. java.lang.OutOfMemoryError: Java heap space
This is the most common form. It occurs when the JVM heap memory is exhausted and no more memory is available for object allocation.
Example Code:
Fixes:
-
Increase Heap Size: Modify JVM options:
-
Profile Your Application: Use tools like VisualVM or Eclipse MAT to detect memory leaks or large object retention.
-
Code Fix: Avoid unbounded collections.
2. java.lang.OutOfMemoryError: GC overhead limit exceeded
This error indicates that the JVM is spending too much time performing garbage collection and has recovered very little memory.
Code to Reproduce:
Fixes:
-
Memory Leak Analysis: Use a memory profiler to find object references that are not being released.
-
Increase Heap or Tune GC:
Be careful with disabling
UseGCOverheadLimit
, as it may mask deeper issues. -
Implement Eviction Logic:
3. java.lang.OutOfMemoryError: Metaspace
From Java 8 onwards, class metadata is stored in Metaspace instead of PermGen. This error happens when too many classes are loaded dynamically.
Example Scenario: This commonly occurs in applications that use frameworks like Spring or Hibernate improperly, or when deploying multiple applications on the same JVM (like in a servlet container).
Fixes:
-
Increase Metaspace Size:
-
Unload Classes Properly: If using custom class loaders, ensure classes are correctly unloaded.
-
Inspect Class Loading: Tools like JVisualVM and jcmd can show loaded classes:
4. java.lang.OutOfMemoryError: Direct buffer memory
Occurs when using ByteBuffer.allocateDirect()
and the allocated memory exceeds the -XX:MaxDirectMemorySize
.
Example Code:
Fixes:
-
Tune Direct Memory Size:
-
Reuse Buffers: Instead of creating new ones in loops, pool and reuse buffers.
5. java.lang.OutOfMemoryError: unable to create new native thread
This error typically occurs when the JVM reaches the OS thread limit. Each thread consumes native memory, and too many threads can exhaust system resources.
Example Code:
Fixes:
-
Thread Pooling: Use
Executors.newFixedThreadPool()
to limit thread creation. -
Monitor OS Limits: On Unix:
-
Use Lightweight Concurrency Models: Consider using asynchronous APIs or frameworks like Vert.x or Project Loom (virtual threads).
How to Diagnose OutOfMemoryError
s
-
Heap Dumps:
-
Use
-XX:+HeapDumpOnOutOfMemoryError
to generate a.hprof
file on crash.
-
-
Monitoring Tools:
-
JConsole, VisualVM, JProfiler, and YourKit can visualize memory usage.
-
Java Flight Recorder (JFR) provides low-overhead production profiling.
-
-
Logs and Error Messages:
-
Check logs for stack traces and memory pool exhaustion.
-
Use
-verbose:gc
and-Xlog:gc*
to trace GC activity.
-
Best Practices to Prevent OutOfMemoryError
-
Limit Cache Size: Use
LinkedHashMap
with eviction policy orGuava Cache
. -
Use Weak/Soft References:
-
Avoid Large Object Allocation: Chunk large data processing instead of loading everything into memory.
-
Properly Close Resources: Unclosed streams and JDBC connections can hold references.
-
Deploy Application with Suitable JVM Options: Tune heap, stack size, Metaspace, and GC for your app’s specific needs.
Conclusion
OutOfMemoryErrors are often a sign of deeper issues within a Java application—whether it’s a memory leak, inefficient data structures, unbounded caching, thread mismanagement, or improper JVM configurations.
Understanding the different memory areas (heap, metaspace, direct memory, native threads) and how the JVM manages them is crucial to diagnosing the root causes. While increasing memory limits may seem like a quick fix, it’s usually more effective to profile and optimize the application’s memory usage.
By applying proactive techniques such as memory profiling, heap dump analysis, proper use of collections, thread management, and JVM tuning, developers can build applications that are not only stable under normal loads but also resilient under high stress.
Finally, consider integrating monitoring solutions (like Prometheus with Grafana, or APM tools like New Relic and AppDynamics) into production environments to catch memory trends early—before they bring down your application.
Java’s memory management is powerful, but not foolproof. With the right diagnostics and fixes, OutOfMemoryError
s can go from fatal runtime problems to manageable hiccups in your software development lifecycle.