Why JVM Options Matter (Dev vs Production)
In development, we want fast feedback and simple defaults. In production, we care about:
- Stable performance (low latency, predictable throughput)
- Controlled memory usage (avoid OOM and excessive GC)
- Observability (GC logs, heap dumps, JFR, JMX metrics)
- Safe rollouts (repeatable flags, minimal risk)
JVM flags can make or break a system—especially under traffic spikes.
How to Pass JVM Options (Quick Examples)
Run a Java app with JVM flags
java -Xms512m -Xmx512m -jar app.jar
Spring Boot (recommended production layout)
Use JAVA_TOOL_OPTIONS or JAVA_OPTS in your service config:
export JAVA_TOOL_OPTIONS="-Xms512m -Xmx512m -XX:+UseG1GC"
java -jar app.jar
In Docker/Kubernetes (typical)
Use environment variable:
JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75 -XX:+UseG1GC"
Core Memory Options: -Xms and -Xmx
-Xms
Initial heap size.
Example:
-Xms512m
-Xmx
Maximum heap size.
Example:
-Xmx2g
Production best practices
- For stable performance, many teams keep them equal:
-Xms2g -Xmx2gThis reduces heap resizing pauses and gives predictable GC behavior. - In containers, prefer percentage-based sizing:
-XX:MaxRAMPercentage=75 -XX:InitialRAMPercentage=50This plays better with Kubernetes memory limits.
Useful Memory & Diagnostics Flags
Print JVM flag values (helps confirm what’s really applied)
java -XX:+PrintFlagsFinal -version
Show VM settings
java -XshowSettings:vm -version
java -XshowSettings:system -version
Heap dump on OutOfMemoryError (production must-have)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/myapp/heapdumps
Exit on OOM (useful for auto-restart environments like K8s)
-XX:+ExitOnOutOfMemoryError
Garbage Collection (GC) Tuning: What to Use Today
Quick recommendation (most services)
For modern Java (11/17/21), G1GC is a safe default for most server apps:
-XX:+UseG1GC
Low-latency use cases
If you have strict latency SLOs and can test thoroughly:
- ZGC (very low pauses, needs careful benchmarking)
-XX:+UseZGC - Shenandoah (also low pause, depends on distribution)
Rule: pick a GC, then measure before tuning aggressively.
GC Logging (Production-grade)
GC logs are one of the best tools for diagnosing memory issues.
Java 9+ unified logging (recommended)
-Xlog:gc*,safepoint:file=/var/log/myapp/gc.log:time,level,tags:filecount=10,filesize=50M
What you learn from GC logs
- GC frequency (too often = memory pressure)
- Pause times (latency impact)
- Promotion failures / allocation stalls
- Old-gen growth (possible memory leak)
Common GC Tuning Flags (Use Carefully)
Target pause goal (G1)
-XX:MaxGCPauseMillis=200
This is a goal, not a guarantee.
Start GC earlier (G1) to avoid sudden long cycles
-XX:InitiatingHeapOccupancyPercent=30
Limit number of GC threads (sometimes useful in small CPUs)
-XX:ParallelGCThreads=4
-XX:ConcGCThreads=2
Tuning without load-testing is dangerous—always validate with realistic traffic.
Metaspace Tuning (Class metadata)
Metaspace stores class metadata (not part of heap).
-XX:MaxMetaspaceSize=256m
Be careful: setting it too low can cause OutOfMemoryError: Metaspace.
Thread Stack: -Xss
Controls stack size per thread.
-Xss512k
- Smaller stack = you can run more threads
- Too small =
StackOverflowErrorrisk
JMX (Java Management Extensions) for Monitoring
JMX lets you expose JVM and app metrics (threads, memory pools, GC, MBeans).
Local JMX (dev)
Usually no extra flags needed—tools like VisualVM or JConsole can attach locally.
Remote JMX (production – be careful!)
If you must use it, secure it properly (network policies + auth + TLS). Typical flags look like:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.rmi.port=9010
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.ssl=true
-Djava.rmi.server.hostname=YOUR_HOSTNAME
Production guidance
- Prefer Prometheus + JMX exporter or OpenTelemetry rather than opening remote JMX directly.
- If opening remote JMX, restrict it with firewall/VPC/security groups.
JNLP (Java Web Start): What It Is (and Modern Reality)
JNLP was used to launch Java applications from browsers via Java Web Start.
Example old-style start:
javaws app.jnlp
Important (modern reality):
- Oracle removed Java Web Start from the JDK long ago.
- Today, if you still need JNLP-style launching, teams use alternatives like IcedTea-Web or migrate to packaged desktop apps (jpackage) or web apps.
So for “production-grade” in 2026, JNLP is usually legacy; include it only if you maintain older enterprise clients.
Troubleshooting Commands You Should Know
Check Java version + vendor
java -version
Find JVM process ID (PID)
jps -l
Print JVM command line flags used by a running process
jcmd <pid> VM.command_line
Thread dump (when app is stuck / high CPU)
jstack <pid> > threaddump.txt
Or safer in production:
jcmd <pid> Thread.print > threaddump.txt
Heap histogram (who uses memory)
jcmd <pid> GC.class_histogram > histo.txt
Trigger heap dump (investigation)
jcmd <pid> GC.heap_dump /var/log/myapp/heap.hprof
Production-Ready JVM Flag Templates
1) Standard microservice (Java 17/21, G1GC, observability)
JAVA_TOOL_OPTIONS="
-XX:+UseG1GC
-XX:MaxRAMPercentage=75
-XX:InitialRAMPercentage=50
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/myapp/heapdumps
-XX:+ExitOnOutOfMemoryError
-Xlog:gc*,safepoint:file=/var/log/myapp/gc.log:time,level,tags:filecount=10,filesize=50M
"
2) Fixed heap (VM-based deployment, predictable behavior)
JAVA_TOOL_OPTIONS="
-Xms2g -Xmx2g
-XX:+UseG1GC
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/myapp/heapdumps
-Xlog:gc*:file=/var/log/myapp/gc.log:time,level,tags
"
3) Low-latency experiment (requires benchmarking)
JAVA_TOOL_OPTIONS="
-XX:+UseZGC
-XX:MaxRAMPercentage=75
-Xlog:gc*:file=/var/log/myapp/gc.log:time,level,tags
"
A Simple Tuning Checklist (Practical)
- Set container memory limits (or VM RAM) correctly.
- Choose heap strategy:
- Fixed:
-Xms = -Xmx(stable) - Container aware:
MaxRAMPercentage
- Fixed:
- Enable GC logs + heap dump on OOM.
- Watch:
- GC frequency, pause times, old-gen growth
- CPU usage, thread counts, allocation rate
- Only then tune:
- G1 pause goals, IHOP, thread counts, metaspace caps
- Validate changes using load testing and compare before/after.
FAQ
Should I always set -Xms equal to -Xmx?
Often yes for server workloads, but in containers percentage-based settings can be more flexible.
Is GC tuning mandatory?
No. Good observability is mandatory. Tuning is optional unless you see a real problem.
Is JMX safe in production?
Only if secured. Prefer metrics exporters/telemetry rather than exposed remote ports.
Is JNLP still used?
Mostly legacy. For modern desktop deployments, consider jpackage or a web-based approach.
Conclusion
For production-grade Java, the most impactful “commands” are not exotic GC flags—it’s getting the basics right:
- sensible heap sizing (
-Xms/-Xmxor RAM percentages), - GC logs,
- heap dumps on OOM,
- safe observability (JMX exporter / telemetry),
- and measuring under real load.