Home / Fix an issue

Fix an issue

Java Exit Code 137: Why the Container Is Killed and How to Fix It

No stack trace, no error, just a restart. Exit code 137 is the signature of a process killed from outside, and on small hosts the reason is almost always memory.

Checked November 2026

A glass server cube overflowing with violet liquid that runs down onto its plinth

Exit code 137 is 128 plus 9, and 9 is SIGKILL: the process was stopped from outside and had no chance to clean up or log anything. In a container this almost always means memory. Docker's documentation is plain about it: by default, if an out-of-memory error occurs, the kernel kills processes in the container. On platforms that restart crashed apps, the result is a loop of start, run for a while, disappear, start again.

Platform behaviour checked in November 2026.

How to confirm it is memory

Look for three signs. The runtime log ends in the middle of normal output, with no Java exception. The platform shows the instance as restarted, or marks the event as out of memory. And memory graphs, where the platform has them, climb to the plan's limit just before each restart.

On your own Docker host, docker inspect shows "OOMKilled": true for the stopped container. Managed platforms usually show the same event in their dashboard or event log.

Why the JVM goes over the limit

The heap is only part of a Java process. Besides the objects your code creates, the JVM needs memory for loaded classes (metaspace), compiled code (the code cache), one stack per thread, garbage collector bookkeeping, and direct buffers used by networking libraries. On a small Spring Boot app this non-heap part can be a large share of the total.

A fixed -Xmx copied from a laptop setup is the classic mistake. -Xmx1g in a 512 MB container cannot work, and even -Xmx512m leaves nothing for the rest of the process. The container hits its limit while the heap still has room, so the kernel acts before Java notices anything.

Size the heap from the container

Modern JVMs read the container's memory limit. Instead of a fixed size, give the heap a percentage of it:

java -XX:MaxRAMPercentage=70 -jar app.jar

The default for MaxRAMPercentage is 25 percent, which wastes memory in a container that runs a single app. Raising it is right, but not to 100: the remaining share is what metaspace, threads and buffers live in. Start around 60 to 75 percent and adjust after measuring.

To see where the memory goes, start the app with native memory tracking and ask the running JVM for a summary:

java -XX:NativeMemoryTracking=summary -XX:MaxRAMPercentage=70 -jar app.jar
jcmd <pid> VM.native_memory summary

The output lists heap, class, thread and code memory separately, which tells you whether to shrink the heap, reduce threads or drop libraries.

The arithmetic on a 512 MB plan

Numbers make the problem concrete. On an instance with a 512 MB limit, the default MaxRAMPercentage of 25 percent caps the heap at 128 MB. That is often too small for a Spring Boot app, which then fails with a real OutOfMemoryError or spends its time in garbage collection.

With -XX:MaxRAMPercentage=70, the heap may grow to about 358 MB. The remaining 154 MB has to hold metaspace, the code cache, every thread stack and any direct buffers. Whether that is enough depends on the app, which is why the native memory summary above matters more than any rule of thumb.

Test the same limit on your machine before you deploy:

docker run --rm -m 512m -e PORT=8080 -p 8080:8080 myapp

Then send it the kind of traffic it will see in production. A container that survives a few minutes of realistic load at -m 512m locally will usually survive on a 512 MB plan as well.

Reduce what the app holds

If the numbers show the app genuinely needs more than the plan offers, look at the usual suspects before you upgrade. Thread pools sized for a big server create many threads, each with its own stack. Caches without a size limit grow until something stops them. Loading a whole file or result set into memory, instead of streaming it, creates spikes. Each of these is cheaper to fix than a bigger plan, and each also makes the app faster.

Make real heap problems visible

Exit code 137 hides the cause; an OutOfMemoryError shows it. Two flags help when the heap itself is the problem:

java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/app.hprof \
     -XX:+ExitOnOutOfMemoryError -jar app.jar

The first writes a heap dump you can open in a profiler. The second stops the JVM cleanly so the platform restarts it, instead of leaving a half-working app. Keep in mind that a dump written to an ephemeral disk is lost on restart, so copy it off before the next deploy.

When to change the plan

Sizing helps only up to the plan's limit. If the measured need, heap plus non-heap, is above it, choose a plan with more memory. The hosting comparison lists memory per plan, and the Java Launchpad filters plans by the memory you need. A JVM that has to start again after every kill also starts slowly each time, covered on the slow first request page.

Sources

  1. docs.docker.com/engine/containers/resource_constraints
  2. docs.oracle.com/en/java/javase/25/docs/specs/man/java.html

Fix an issue / FAQ

Good questions.
Clear answers.

What does exit code 137 mean?

It is 128 plus signal 9, SIGKILL. The process did not exit on its own; something stopped it. In a container, the usual cause is the kernel's out-of-memory killer, which stops processes when the container goes over its memory limit.

Why is there no OutOfMemoryError in the log?

An OutOfMemoryError is thrown when the Java heap is full. Exit code 137 happens when the whole process, heap plus everything outside it, goes over the container limit. The kernel stops it before the JVM can report anything.

What heap size should I use on a 512 MB instance?

Leave room for memory outside the heap. Setting -XX:MaxRAMPercentage instead of a fixed -Xmx lets the JVM size the heap from the container limit. The default is 25 percent; values around 60 to 75 percent are common for a single app per container, but measure your own app's non-heap use first.

Should I just buy a bigger plan?

Sometimes that is the honest fix. First check whether the heap is sized for the container and whether the app holds data it does not need. If the measured need is above the plan's limit, more memory is cheaper than the time spent squeezing.

Find your next step.