Published on

Java - OutOfMemoryError

The OutOfMemoryError is thrown from Java program when there is not enough space to allocate the requested memory by the program. Some GCs (like G1) tries to garbage collect if it sees there is not enough memory and then try to re-allocate.

But does OOM bring down the entire application? Let's find out!

The below given program keeps allocating bytes until the JVM runs out of memory. We can see the OOM thrown from the main thread and it exists.

OOM killing main thread
$ javac DoesOOMKillTheProgram.java && java -Xms256m -Xmx1024m DoesOOMKillTheProgram 1000000000 # Yes it does!
---------------------
free: 256 MB; total: 258 MB; max: 1024 MB
Bytes allocated for current thread 788936 (0 MB)
CPU time 32411000 nanos


Trying to allocate 1000000000 bytes (953 MB)
---------------------
free: 67 MB; total: 1024 MB; max: 1024 MB
Bytes allocated for current thread 1001196928 (954 MB)
CPU time 114885000 nanos
Trying to allocate 1000000000 bytes (953 MB)
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at DoesOOMKillTheProgram.allocateUntilOOM(DoesOOMKillTheProgram.java:46)
        at DoesOOMKillTheProgram.main(DoesOOMKillTheProgram.java:10)

DoesOOMKillTheProgram.java

public class DoesOOMKillTheProgram {
	private static final int MEGA_BYTES = 1024 * 1024;
	private static final java.util.Scanner scanner = new java.util.Scanner(System.in);

	public static void main(String[] $) throws Exception {
		int bytes = Integer.parseInt($[0]);

		printStats();
		allocateUntilOOM(bytes);

        System.out.println("Exiting");
	}

	private static void allocateUntilOOM(int bytes) {
        int n = 100000;
        byte[][] all_bytes = new byte[n][];
        for (int i = 0; i < n; ++i) {
            System.out.println(String.format(
                        "Trying to allocate %d bytes (%d MB)", bytes, bytes / MEGA_BYTES));

            byte[] allocating_here = new byte[bytes];
            all_bytes[i] = allocating_here;      // Storing it in an arry to avoid getting g

            printStats();
        }
	}

	private static void printStats() {
		line();
		printMemoryStats();
		printThreadMemoryStats();
	}

	private static void printMemoryStats() {
		long freeMemory = Runtime.getRuntime().freeMemory() / MEGA_BYTES;
		long totalMemory = Runtime.getRuntime().totalMemory() / MEGA_BYTES;
		long maxMemory = Runtime.getRuntime().maxMemory() / MEGA_BYTES;

		System.out.println(String.format(
					"free: %d MB; total: %d MB; max: %d MB",
					freeMemory, totalMemory, maxMemory
					));
	}

	private static void printThreadMemoryStats() {
		com.sun.management.ThreadMXBean threadMXBean =
			(com.sun.management.ThreadMXBean)
			java.lang.management.ManagementFactory.getThreadMXBean();

		long threadBytes =
					threadMXBean.getThreadAllocatedBytes(Thread.currentThread().getId());

		System.out.println(String.format(
					"Bytes allocated for current thread %d (%d MB)",
					threadBytes, threadBytes / MEGA_BYTES
					));

		System.out.println(String.format(
					"CPU time %d nanos",
					threadMXBean.getCurrentThreadCpuTime()
					));
	}

	private static void line() {
		System.out.println("---------------------");
	}
}

Catch the Error?

You can handle the error thrown from the JVM and can safely avoid it. But it is probably a bad idea.

But you can chose to handle it
try {
    allocateUntilOOM(bytes);
} catch (Error e) {
    System.out.println("Handling the OOM");
    System.gc();
}

$ javac DoesOOMKillTheProgram.java && java -Xms256m -Xmx1024m DoesOOMKillTheProgram 1000000000
---------------------
free: 255 MB; total: 258 MB; max: 1024 MB
Bytes allocated for current thread 789584 (0 MB)
CPU time 30317000 nanos
Trying to allocate 1000000000 bytes (953 MB)
---------------------
free: 67 MB; total: 1024 MB; max: 1024 MB
Bytes allocated for current thread 1001197576 (954 MB)
CPU time 112770000 nanos
Trying to allocate 1000000000 bytes (953 MB)
Handling the OOM
Hey bro!. I can still run fine
Exiting

Multi-threaded env

What happens when the error is thrown from only one of the threads?

Let's create a thread myThread1 and try to allocate memory until OOM occurs. Sleep the main thread until OOM occurs in another thread and then continue.

When the OOM is thrown, the main thread is not affected and only myThread1 dies.

Only the thread dies!
$ javac DoesOOMKillTheProgram.java && java -Xms256m -Xmx1024m DoesOOMKillTheProgram 1000000000
---------------------
free: 255 MB; total: 258 MB; max: 1024 MB
Bytes allocated for current thread 789224 (0 MB)
CPU time 29701000 nanos
######################
Trying to allocate 1000000000 bytes (953 MB)  # In myThread1
---------------------
free: 67 MB; total: 1024 MB; max: 1024 MB
Bytes allocated for current thread 1000404648 (954 MB)
CPU time 79966000 nanos
Trying to allocate 1000000000 bytes (953 MB)  # Again in myThread1
Exception in thread "myThread1" java.lang.OutOfMemoryError: Java heap space
        at DoesOOMKillTheProgram.allocateUntilOOM(DoesOOMKillTheProgram.java:46)
        at DoesOOMKillTheProgram$1.run(DoesOOMKillTheProgram.java:23)
Exiting
DoesOOMKillTheProgram.java

public class DoesOOMKillTheProgram {
	private static final int MEGA_BYTES = 1024 * 1024;
	private static final java.util.Scanner scanner = new java.util.Scanner(System.in);

	public static void main(String[] $) throws Exception {
		int bytes = Integer.parseInt($[0]);

		printStats();
        allocateInThread(bytes);
        Thread.sleep(1000);

        System.out.println("Exiting");
	}

    private static void allocateInThread(int bytes) {
        System.out.println("######################");
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    allocateUntilOOM(bytes);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("From thread: " + Thread.currentThread().getId());
            }
        };
        thread.setName("myThread" + Thread.currentThread().getId());
        thread.start();
    }

	private static void allocateUntilOOM(int bytes) {
        int n = 100000;
        byte[][] all_bytes = new byte[n][];
        for (int i = 0; i < n; ++i) {
            System.out.println(String.format(
                        "Trying to allocate %d bytes (%d MB)", bytes, bytes / MEGA_BYTES));

            byte[] allocating_here = new byte[bytes];
            all_bytes[i] = allocating_here;             // Storing it in an arry to avoid getting gc

            printStats();
        }
	}

	private static void printStats() {
		line();
		printMemoryStats();
		printThreadMemoryStats();
	}

	private static void printMemoryStats() {
		long freeMemory = Runtime.getRuntime().freeMemory() / MEGA_BYTES;
		long totalMemory = Runtime.getRuntime().totalMemory() / MEGA_BYTES;
		long maxMemory = Runtime.getRuntime().maxMemory() / MEGA_BYTES;

		System.out.println(String.format(
					"free: %d MB; total: %d MB; max: %d MB",
					freeMemory, totalMemory, maxMemory
					));
	}

	private static void printThreadMemoryStats() {
		com.sun.management.ThreadMXBean threadMXBean =
			(com.sun.management.ThreadMXBean)
			java.lang.management.ManagementFactory.getThreadMXBean();

		long threadBytes =
					threadMXBean.getThreadAllocatedBytes(Thread.currentThread().getId());

		System.out.println(String.format(
					"Bytes allocated for current thread %d (%d MB)",
					threadBytes, threadBytes / MEGA_BYTES
					));

		System.out.println(String.format(
					"CPU time %d nanos",
					threadMXBean.getCurrentThreadCpuTime()
					));
	}

	private static void line() {
		System.out.println("---------------------");
	}
}

If you want to bring down the application whenever OOM occurs anywhere, you can use one of the below JVM args.

  1. -XX:+ExitOnOutOfMemoryError
  2. -XX:+CrashOnOutOfMemoryError