Skip to main content

Java OutOfMemoryError: Handle Java OutOfMemoryErrors

1561 words·
Java OutOfMemoryError ExitOnOutOfMemoryError HeapDump Docker

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