# Playing with Java Out Of Memory Exception

I was recently trying to simulate `java.lang.OutOfMemoryError` in my local. This was to learn and also understand how to deal with them in case they happen in our [Togai](https://www.togai.com/) production environment

My idea was to run a code that takes up a lot of memory in the heap. And I wanted to do this in a controlled/sandboxed environment because I didn't want to use up all the 16GB memory in my work laptop 💻 using a Java program to just see `java.lang.OutOfMemoryError` exception in my local

Controlled/sandboxed environment - container? VM (Virtual Machine)? micro VM? I chose container because that's too easy and quick to spin up, some VMs and micro VMs are easy and quick to spin up too by the way

Now, I know where to run the Java code or Kotlin or any code for that matter, that can be compiled and run in JVM (Java Virtual Machine). I have Docker installed on my machine. Now I just need the code that can take up lots of memory in the heap. I did a quick Google search and found a nice little program here - [https://mkyong.com/java/how-to-simulate-java-lang-outofmemoryerror-in-java/](https://mkyong.com/java/how-to-simulate-java-lang-outofmemoryerror-in-java/) by  
[Yong Mook Kim](https://github.com/mkyong)

```java
// JavaEatMemory.java
import java.util.ArrayList;
import java.util.List;

public class JavaEatMemory {

	public static void main(String[] args) {

			List<byte[]> list = new ArrayList<>();
			int index = 1;
			while (true) {
					// 1MB each loop, 1 x 1024 x 1024 = 1048576
					byte[] b = new byte[1048576];
					list.add(b);
					Runtime rt = Runtime.getRuntime();
					System.out.printf("[%d] free memory: %s%n", index++, rt.freeMemory());
			}

	}
}
```

I saved this in a file named `JavaEatMemory.java`

Next, I wrote a `Dockerfile` to build the code and run it

```dockerfile
FROM eclipse-temurin:11 AS builder
WORKDIR /app
COPY JavaEatMemory.java /app
RUN javac JavaEatMemory.java

FROM eclipse-temurin:11
WORKDIR /app
COPY --from=builder /app/JavaEatMemory.class /app
CMD ["java", "-Xmx5000K", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/app/heap-dumps", "JavaEatMemory"]
```

As you can see I built (compiled) the code inside the Docker container itself using the [multi-stage build](https://docs.docker.com/build/building/multi-stage/) feature, so if you are trying this, you don't even need Java (Java Development Kit, Java Runtime Environment etc) installed on your machine

I have used `eclipse-temurin` JDK distribution and used Java `v11` because that's the one we are running in our [Togai](https://www.togai.com/) production environment. You can choose any JDK distribution and any Java version. Note that I have used some extra options in the `java` command and options differ/are based on the JDK distribution from what I read [here](https://stackoverflow.com/a/24812046/4772008)

Let me explain some of the options I have used here

`Xmx` - I used this to set a max size for the heap. Proper documentation can be found by using Java Oracle docs / whatever docs of distribution you use, or `man java` / `man java | cat` which shows the below content

```bash
       -Xmxsize
           Specifies the maximum size (in bytes) of the memory allocation pool in bytes. This value must be a
           multiple of 1024 and greater than 2 MB. Append the letter k or K to indicate kilobytes, m or M to
           indicate megabytes, g or G to indicate gigabytes. The default value is chosen at runtime based on system
           configuration. For server deployments, -Xms and -Xmx are often set to the same value. See the section
           "Ergonomics" in Java SE HotSpot Virtual Machine Garbage Collection Tuning Guide at
           http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html.

           The following examples show how to set the maximum allowed size of allocated memory to 80 MB using
           various units:

               -Xmx83886080
               -Xmx81920k
               -Xmx80m

           The -Xmx option is equivalent to -XX:MaxHeapSize.
```

`-XX:+HeapDumpOnOutOfMemory` - as the name suggests - was used to take a dump of the heap when `java.lang.OutOfMemoryError` error occurs

`-XX:HeapDumpPath` - as the name suggests - when taking a dump of the heap, the path to store the dump can be mentioned using this option

Again, these options can be found using `man java` or `man java | cat`

```bash
       -XX:+HeapDumpOnOutOfMemory
           Enables the dumping of the Java heap to a file in the current directory by using the heap profiler
           (HPROF) when a java.lang.OutOfMemoryError exception is thrown. You can explicitly set the heap dump file
           path and name using the -XX:HeapDumpPath option. By default, this option is disabled and the heap is not
           dumped when an OutOfMemoryError exception is thrown.

       -XX:HeapDumpPath=path
           Sets the path and file name for writing the heap dump provided by the heap profiler (HPROF) when the
           -XX:+HeapDumpOnOutOfMemoryError option is set. By default, the file is created in the current working
           directory, and it is named java_pidpid.hprof where pid is the identifier of the process that caused the
           error. The following example shows how to set the default file explicitly (%p represents the current
           process identificator):

               -XX:HeapDumpPath=./java_pid%p.hprof

           The following example shows how to set the heap dump file to /var/log/java/java_heapdump.hprof:

               -XX:HeapDumpPath=/var/log/java/java_heapdump.hprof
```

By the way, I wanted to use these options in production so I wanted a way to test them locally first by using the same JDK distribution and version as the one in production. So, this was another reason to try it in my local to test if these options will work whenever `java.lang.OutOfMemoryError` error happens in our Togai production environment. We can't [test in production](https://increment.com/testing/i-test-in-production/) now, [can we](https://www.infoq.com/presentations/testing-production-2018/)? :P

Anyways, now we have a `Dockerfile`, we can build it using `docker` CLI command and the Docker Engine (Docker daemon) running behind the scenes

```bash
docker build -t out-of-memory-jar .
```

This will build the Docker container image we require. Let's now run the Docker container image

```bash
mkdir heap-dumps # to store the heap dumps

docker run --rm --memory 10MB -v $(pwd)/heap-dumps:/app/heap-dumps  out-of-memory-jar
```

You should get an output like this -

```bash
$ docker run --rm --memory 10MB -v $(pwd)/heap-dumps:/app/heap-dumps  out-of-memory-jar
[1] free memory: 4507776
[2] free memory: 3352800
[3] free memory: 2304320
[4] free memory: 1255872
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /app/heap-dumps/java_pid1.hprof ...
Heap dump file created [6784892 bytes in 0.131 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at JavaEatMemory.main(JavaEatMemory.java:12)
```

And you should be able to see the heap dump in the `heap-dumps` directory

```bash
$ ls -lh heap-dumps 
total 13256
-rw-------@ 1 karuppiah  staff   6.5M Jul 25 20:46 java_pid1.hprof
```

That's all, go on and experiment with this code and different options to try anything related to Java out-of-memory errors or other Java stuff too. Maybe create a blog post for a similar experiment but for another programming language like [Golang](https://golang.org/), [Ruby](https://www.ruby-lang.org/en/), [Crystal](https://crystal-lang.org/), [Ziglang](https://ziglang.org/), [Erlang](https://www.erlang.org/), [Elixir](https://elixir-lang.org/), [Haskell](https://www.haskell.org/) etc :D or for Java itself ;) or something like [Kotlin](https://kotlinlang.org/), [Clojure](https://clojure.org/) etc languages that run on JVM

Some extra experiments that I did in the above setup

### Set a very low value for the maximum heap size

I set `-Xmx` as `500K`, like this -

```dockerfile
FROM eclipse-temurin:11 AS builder
WORKDIR /app
COPY JavaEatMemory.java /app
RUN javac JavaEatMemory.java

FROM eclipse-temurin:11
WORKDIR /app
COPY --from=builder /app/JavaEatMemory.class /app
CMD ["java", "-Xmx500K", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/app/heap-dumps", "JavaEatMemory"]
```

When I built the Docker container image and ran the container, the output was like this -

```bash
$ docker build -t out-of-memory-jar .
```

```bash
$ docker run --rm --memory 10MB out-of-memory-jar
Error occurred during initialization of VM
Too small maximum heap

$ echo $?
1
```

As you can see, JVM throws an error saying that the maximum heap size is too small

The JVM kept throwing the same error when I tried to increase the maximum heap size to say `600K`, or even `1000K` and then even `2000K`

Finally, I noticed that the JVM does not throw an error when the maximum heap size is set to `2048K` which is `2MB` when you consider `1MB` as `1024KB`. So I guess for this JVM version and distribution, the minimum value of the maximum heap size is `2048K`

### Set a very low value for the Docker container memory limit

When I tried a value like `5MB` for the Docker container running my notorious Java program that eats memory, this is the output I got -

```bash
$ docker run --rm --memory 5MB out-of-memory-jar
docker: Error response from daemon: Minimum memory limit allowed is 6MB.
See 'docker run --help'.
```

Looks like the minimum memory limit that I can set with this version of Docker Engine / Daemon is `6MB`

### Set a higher value for the maximum heap size than the Docker container memory limit

For example, I set `15000K` (around `15MB`) as the maximum heap size

```dockerfile
FROM eclipse-temurin:11 AS builder
WORKDIR /app
COPY JavaEatMemory.java /app
RUN javac JavaEatMemory.java

FROM eclipse-temurin:11
WORKDIR /app
COPY --from=builder /app/JavaEatMemory.class /app
CMD ["java", "-Xmx15000K", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/app/heap-dumps", "JavaEatMemory"]
```

And then I built the Docker container image and ran the Docker container with `10MB` memory limit - which is lower than the maximum heap size we just set

```bash
$ docker build -t out-of-memory-jar .;
```

And the output I saw was this -

```bash
$ docker run --rm --memory 10MB out-of-memory-jar
[1] free memory: 6366272
[2] free memory: 5371968
[3] free memory: 4323376
[4] free memory: 3402888
[5] free memory: 2354296
[6] free memory: 7237408
[7] free memory: 6188816
[8] free memory: 5140224

$ echo $?
137
```

We can see the [137 exit code](https://itsfoss.com/linux-exit-codes/#code-137-or-sigkill) here, which means the process was killed with a `SIGKILL` signal. I guess the operating system used the out-of-memory killer (OOM Killer) to kill the process taking up too much memory

This shows us that it's important to tell the JVM a valid maximum heap size - something less than the total memory available in the environment (e.g. container, physical machine, VM etc) and not something more than the total memory available. Even when I gave an equal memory limit for the container and the JVM maximum heap size, the process got killed

For you to get the `java.lang.OutOfMemoryError` error and a heap dump when it happens, to check for any issues in heap, it's important to set a valid value for the maximum heap size
