When you’re running a Java application in production, performance often comes down to how you configure memory. And by “memory,” I mean how the JVM handles it—not just how much RAM your machine has.
It’s easy to launch a Spring Boot app with a java -jar, but if you’re not passing the right flags, you’re leaving performance—and stability—on the table. Here are three JVM parameters you absolutely should understand and configure.
1. Heap Size: Control the Playground
At the core of every Java app is the heap: the memory space where objects live. If you don’t set boundaries here, the JVM will guess for you. Spoiler: it usually guesses wrong.
-Xms512m # Initial heap size
-Xmx2048m # Maximum heap size
Why it matters?
- Too low: frequent garbage collection, high CPU usage, poor throughput.
- Too high: memory bloat, long GC pauses, possibly OOM if the host runs out of physical RAM.
My approach? Always set -Xms and -Xmx to the same value in production. This avoids heap resizing and keeps performance consistent.
2. Out of Memory Handling: Prepare for Failure
Let’s be honest, at some point, your app will run out of memory. Either due to a memory leak, an unexpected load spike, or just bad luck. The worst thing you can do is be unprepared for it.
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/app/heapdump.hprof
-XX:+ExitOnOutOfMemoryError
Why it matters?
- HeapDumpOnOutOfMemoryError lets you inspect what filled up the memory.
- HeapDumpPath gives you control over where the file lands (and avoids polluting your working directory).
- ExitOnOutOfMemoryError ensures the process exits cleanly, allowing orchestration tools (e.g., Kubernetes) to restart it.
It’s not about avoiding memory issues entirely—it’s about making sure they don’t take down your entire system unnoticed.
3. Garbage Collector: Pick the Right Cleaner
The GC is the JVM’s janitor. And like any janitor, how fast and efficient it works depends on the tools you give it. Not all collectors are created equal, and using the default may not be optimal for your use case.
-XX:+UseG1GC # Balanced throughput + low pause
-XX:+UseZGC # Ultra-low pause, modern hardware
-XX:+UseParallelGC # High throughput, batch workloads
Choosing the right one:
- G1GC: Good general-purpose GC. I use it for most web apps.
- ZGC: Great for apps that require ultra-low latency. But needs a modern JDK (15+).
- ParallelGC: Effective for batch jobs or apps where pause time is less critical.
Test in your environment. GC behavior is deeply tied to your object lifecycle, hardware, and workload patterns.
It’s All in the Flags
Every JVM option covered here can—and should—be passed via command-line when launching your app:
java -Xms512m -Xmx2048m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/app/heapdump.hprof \
-XX:+ExitOnOutOfMemoryError \
-XX:+UseG1GC \
-jar app.jar
These three areas—heap sizing, memory error handling, and GC choice—are low-hanging fruit. Configure them properly, and your JVM will behave much more predictably under pressure.



Leave a comment