This blog post demonstrates how to reproduce Java OutOfMemoryErrors and how to handle them, both locally and in a containerized environment.
Prerequisites #
Install Java SDK #
# Install Java Developmen OpenJDK version 21
sudo apt install openjdk-21-jdk -y
# Verify version
java -version
# Shell output:
openjdk version "21.0.6" 2025-01-21
OpenJDK Runtime Environment (build 21.0.6+7-Ubuntu-124.04.1)
OpenJDK 64-Bit Server VM (build 21.0.6+7-Ubuntu-124.04.1, mixed mode, sharing)
Java OOM App (Local Version) #
Create Project Folder & File Structure #
# Create a project folder
mkdir -p java-app/com/crunchify/tutorials/ && cd java-app
# Create app file
vi com/crunchify/tutorials/CrunchifyGenerateOOM.java
The file and folder structure should look like this:
java-app
└── com
└── crunchify
└── tutorials
├── CrunchifyGenerateOOM.class
└── CrunchifyGenerateOOM.java
Java App: Stop after OOM Error #
I found the following Java code on https://crunchify.com/, the produces an “OnOutOfMemoryError”.
Link: https://crunchify.com/how-to-generate-out-of-memory-oom-in-java-programatically/
package com.crunchify.tutorials;
public class CrunchifyGenerateOOM {
/**
* @author Crunchify.com
* @throws Exception
*
*/
public static void main(String[] args) throws Exception {
CrunchifyGenerateOOM memoryTest = new CrunchifyGenerateOOM();
memoryTest.generateOOM();
}
public void generateOOM() throws Exception {
int iteratorValue = 20;
System.out.println("\n=================> OOM test started..\n");
for (int outerIterator = 1; outerIterator < 20; outerIterator++) {
System.out.println("Iteration " + outerIterator + " Free Mem: " + Runtime.getRuntime().freeMemory());
int loop1 = 2;
int[] memoryFillIntVar = new int[iteratorValue];
// feel memoryFillIntVar array in loop..
do {
memoryFillIntVar[loop1] = 0;
loop1--;
} while (loop1 > 0);
iteratorValue = iteratorValue * 5;
System.out.println("\nRequired Memory for next loop: " + iteratorValue);
Thread.sleep(1000);
}
}
}
Java App: Keep Running after OOM Error #
The following code keeps the Java process running after the “OnOutOfMemoryError” occures.
package com.crunchify.tutorials;
public class CrunchifyGenerateOOM {
/**
* @author Crunchify.com
* @throws Exception
*/
public static void main(String[] args) throws Exception {
CrunchifyGenerateOOM memoryTest = new CrunchifyGenerateOOM();
memoryTest.generateOOM();
}
public void generateOOM() throws Exception {
int iteratorValue = 20;
System.out.println("\n=================> OOM test started..\n");
try {
for (int outerIterator = 1; outerIterator < 20; outerIterator++) {
System.out.println("Iteration " + outerIterator + " Free Mem: " + Runtime.getRuntime().freeMemory());
int loop1 = 2;
int[] memoryFillIntVar = new int[iteratorValue];
// fill memoryFillIntVar array in loop..
do {
memoryFillIntVar[loop1] = 0;
loop1--;
} while (loop1 > 0);
iteratorValue = iteratorValue * 5;
System.out.println("\nRequired Memory for next loop: " + iteratorValue);
Thread.sleep(1000);
}
} catch (OutOfMemoryError e) {
System.err.println("❌ OutOfMemoryError caught: " + e.getMessage());
}
// Keep the app running after OOM
System.out.println("✅ App is still running after OutOfMemoryError...");
while (true) {
Thread.sleep(1000);
}
}
}
Compile & Run #
# Compile the Java app
javac com/crunchify/tutorials/CrunchifyGenerateOOM.java
# Run the Java app with limited memory
java -Xmx4m com.crunchify.tutorials.CrunchifyGenerateOOM
-Xmx4m
Sets the maximum heap size (the memory used for objects) to 4 MB
Shell Output Version 1: Stop after OOM Error
# Shell output:
=================> OOM test started..
Iteration 1 Free Mem: 2947336
Required Memory for next loop: 100
Iteration 2 Free Mem: 2519944
Required Memory for next loop: 500
Iteration 3 Free Mem: 2519944
Required Memory for next loop: 2500
Iteration 4 Free Mem: 2496960
Required Memory for next loop: 12500
Iteration 5 Free Mem: 2496960
Required Memory for next loop: 62500
Iteration 6 Free Mem: 2446944
Required Memory for next loop: 312500
Iteration 7 Free Mem: 2196928
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/jdk.internal.misc.Unsafe.allocateUninitializedArray0(Unsafe.java:1387)
at java.base/jdk.internal.misc.Unsafe.allocateUninitializedArray(Unsafe.java:1380)
at java.base/java.lang.StringConcatHelper.newArray(StringConcatHelper.java:511)
at java.base/java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(DirectMethodHandle$Holder)
at java.base/java.lang.invoke.LambdaForm$MH/0x000077775c006000.invoke(LambdaForm$MH)
at java.base/java.lang.invoke.Invokers$Holder.linkToTargetMethod(Invokers$Holder)
at com.crunchify.tutorials.CrunchifyGenerateOOM.generateOOM(CrunchifyGenerateOOM.java:25)
at com.crunchify.tutorials.CrunchifyGenerateOOM.main(CrunchifyGenerateOOM.java:10)
Shell Output Version 2: Keep Running after OOM Error
# Shell output:
=================> OOM test started..
Iteration 1 Free Mem: 2947336
Required Memory for next loop: 100
Iteration 2 Free Mem: 2519520
Required Memory for next loop: 500
Iteration 3 Free Mem: 2519520
Required Memory for next loop: 2500
Iteration 4 Free Mem: 2496536
Required Memory for next loop: 12500
Iteration 5 Free Mem: 2496536
Required Memory for next loop: 62500
Iteration 6 Free Mem: 2446520
Required Memory for next loop: 312500
Iteration 7 Free Mem: 2196504
❌ OutOfMemoryError caught: Java heap space
✅ App is still running after OutOfMemoryError...
# Stop the Java process
Strg + c
Java OOM App (Container Version) #
Project Folder & File Structure #
The file and folder structure should look like this:
java-app
├── com
│ └── crunchify
│ └── tutorials
│ ├── CrunchifyGenerateOOM.class
│ └── CrunchifyGenerateOOM.java
├── Dockerfile_default
└── Dockerfile_exitonoom
Java App: Keep Running after OOM Error #
# Create a project folder
mkdir -p java-app/com/crunchify/tutorials/ && cd java-app
# Create app file
vi com/crunchify/tutorials/CrunchifyGenerateOOM.java
- CrunchifyGenerateOOM.java
package com.crunchify.tutorials;
public class CrunchifyGenerateOOM {
/**
* @author Crunchify.com
* @throws Exception
*/
public static void main(String[] args) throws Exception {
CrunchifyGenerateOOM memoryTest = new CrunchifyGenerateOOM();
memoryTest.generateOOM();
}
public void generateOOM() throws Exception {
int iteratorValue = 20;
System.out.println("\n=================> OOM test started..\n");
try {
for (int outerIterator = 1; outerIterator < 20; outerIterator++) {
System.out.println("Iteration " + outerIterator + " Free Mem: " + Runtime.getRuntime().freeMemory());
int loop1 = 2;
int[] memoryFillIntVar = new int[iteratorValue];
// fill memoryFillIntVar array in loop..
do {
memoryFillIntVar[loop1] = 0;
loop1--;
} while (loop1 > 0);
iteratorValue = iteratorValue * 5;
System.out.println("\nRequired Memory for next loop: " + iteratorValue);
Thread.sleep(1000);
}
} catch (OutOfMemoryError e) {
System.err.println("❌ OutOfMemoryError caught: " + e.getMessage());
}
// Keep the app running after OOM
System.out.println("✅ App is still running after OutOfMemoryError...");
while (true) {
Thread.sleep(1000);
}
}
}
Dockerfile #
Standard Version #
- Dockerfile_default
# Start from Alpine image
FROM eclipse-temurin:21-jre-alpine AS application
# Install required packages for javac
RUN apk add --no-cache openjdk21 alpine-sdk
# Create app directory structure
WORKDIR /app
# Copy your Java source file
COPY com/crunchify/tutorials/CrunchifyGenerateOOM.java com/crunchify/tutorials/
# Compile the Java source
RUN javac com/crunchify/tutorials/CrunchifyGenerateOOM.java
# Run App with small heap size
CMD ["java", "-Xmx4m", "com.crunchify.tutorials.CrunchifyGenerateOOM"]
ExitOnOutOfMemoryError Version #
The following version adds the -XX:+ExitOnOutOfMemoryError
option, which gracefully shuts down the Java application if an OutOfMemoryError occures:
- Dockerfile_exitonoom
# Start from Alpine image
FROM eclipse-temurin:21-jre-alpine AS application
# Install required packages for javac
RUN apk add --no-cache openjdk21 alpine-sdk
# Create app directory structure
WORKDIR /app
# Copy your Java source file
COPY com/crunchify/tutorials/CrunchifyGenerateOOM.java com/crunchify/tutorials/
# Compile the Java source
RUN javac com/crunchify/tutorials/CrunchifyGenerateOOM.java
# Run App with small heap size
CMD ["java", "-Xmx4m", "-XX:+ExitOnOutOfMemoryError", "com.crunchify.tutorials.CrunchifyGenerateOOM"]
Build the Container Images #
# Build image: Default Version
docker build -t java-oom_default -f Dockerfile_default .
# Build image: ExitOnOOM Version
docker build -t java-oom_exitonoom -f Dockerfile_exitonoom .
Run the Container #
Default Version #
# Run the Docker container
docker run -d \
--name java-oom_default \
java-oom_default:latest &&
docker logs -f java-oom_default
# Shell output:
=================> OOM test started..
Iteration 1 Free Mem: 3002584
Required Memory for next loop: 100
Iteration 2 Free Mem: 2578104
Required Memory for next loop: 500
Iteration 3 Free Mem: 2578104
Required Memory for next loop: 2500
Iteration 4 Free Mem: 2578104
Required Memory for next loop: 12500
Iteration 5 Free Mem: 2578104
Required Memory for next loop: 62500
Iteration 6 Free Mem: 2528088
Required Memory for next loop: 312500
Iteration 7 Free Mem: 2278072
✅ App is still running after OutOfMemoryError...
❌ OutOfMemoryError caught: Java heap space
Verify the Container status, as expected, the container keeps running after the OutOfMemoryError occures:
# List Docker containers
docker ps
# Shell output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8690bfabe9f7 java-oom_default:latest "/__cacert_entrypoin…" 3 minutes ago Up 3 minutes java-oom_default
Exit On OutOfMemoryError Version #
# Run the Docker container
docker run -d \
--restart=always \
--name java-oom_exitonoom \
java-oom_exitonoom:latest
Verify that the container restarts after the “OutOfMemoryError” appears:
# List containers
while true; do
echo "=== $(date) ==="
docker ps
echo
sleep 5
done
# Shell output:
=== Mon Apr 21 10:15:40 AM UTC 2025 ===
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3fc481cca51d java-oom_exitonoom:latest "/__cacert_entrypoin…" 9 seconds ago Up 2 seconds java-oom_exitonoom
=== Mon Apr 21 10:15:45 AM UTC 2025 ===
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3fc481cca51d java-oom_exitonoom:latest "/__cacert_entrypoin…" 14 seconds ago Up 1 second java-oom_exitonoom
=== Mon Apr 21 10:15:50 AM UTC 2025 ===
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3fc481cca51d java-oom_exitonoom:latest "/__cacert_entrypoin…" 19 seconds ago Restarting (3) Less than a second ago java-oom_exitonoom
=== Mon Apr 21 10:15:55 AM UTC 2025 ===
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3fc481cca51d java-oom_exitonoom:latest "/__cacert_entrypoin…" 24 seconds ago Up 4 seconds java-oom_exitonoom
...
# Inspect container: List restarts
docker inspect -f '{{.RestartCount}}' java-oom_exitonoom
# Shell output:
7
Create Heap Dump #
Here I use the docker run command and override the entrypoint to add options that enable heap dump creation and make the container exit when an OutOfMemoryError occurs:
# Create a directory for the heapdump volume mapping
mkdir heapdump
# Run container
docker run --rm \
--name java-oom_default \
--entrypoint java \
-v /home/ubuntu/java-app/heapdump:/app/heapdump \
java-oom_default:latest \
-Xmx4m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/app/heapdump \
-XX:+ExitOnOutOfMemoryError \
com.crunchify.tutorials.CrunchifyGenerateOOM
# Shell output:
=================> OOM test started..
Iteration 1 Free Mem: 3023568
Required Memory for next loop: 100
Iteration 2 Free Mem: 2578104
Required Memory for next loop: 500
Iteration 3 Free Mem: 2578104
Required Memory for next loop: 2500
Iteration 4 Free Mem: 2578104
Required Memory for next loop: 12500
Iteration 5 Free Mem: 2578104
Required Memory for next loop: 62500
Iteration 6 Free Mem: 2528088
Required Memory for next loop: 312500
Iteration 7 Free Mem: 2278072
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /app/heapdump/java_pid1.hprof ...
Heap dump file created [4752068 bytes in 0.011 secs]
Terminating due to java.lang.OutOfMemoryError: Java heap space
Verify the HeapDump file:
# List files
ls -la heapdump/
# Shell output:
-rw------- 1 root root 4752068 Apr 21 10:26 java_pid1.hprof