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