JVM Flags in Java
Introduction
While the Java Virtual Machine (JVM) provides incredible out-of-the-box performance and automatic memory management, it cannot read minds. The JVM does not know if your application is a low-latency trading platform, a high-throughput data processing pipeline, or a memory-constrained microservice.
To optimize the JVM for your specific workload, you use JVM Flags (also known as JVM arguments or options). Passing the right flags at application startup allows you to control memory allocation, configure Garbage Collection behavior, and enable deep diagnostic logging.
This guide covers the most critical JVM flags every professional Java developer should know, categorizing them by purpose.
Types of JVM Flags
JVM flags generally fall into three categories:
- Standard Options (
-): Supported by all JVM implementations (e.g.,-version,-cp). - Non-Standard Options (
-X): Specific to the HotSpot JVM but generally stable across versions (e.g.,-Xmx). - Advanced Options (
-XX): Experimental, internal, or highly specific tuning parameters. They often require a boolean toggle (e.g.,-XX:+UseG1GCto enable,-XX:-UseG1GCto disable).
1. Memory Management Flags
The most common reason developers use JVM flags is to control the Heap Size. If you allocate too little memory, your app will crash with an OutOfMemoryError. If you allocate too much, Garbage Collection pauses can become devastatingly long.
-Xms<size>(Initial Heap Size): Sets the starting size of the heap.-Xmx<size>(Maximum Heap Size): Sets the absolute maximum size the heap can grow to.
Best Practice: In production environments, it is highly recommended to set -Xms and -Xmx to the exact same value (e.g., -Xms4G -Xmx4G). This prevents the JVM from wasting CPU cycles dynamically resizing the heap during runtime.
-
-Xss<size>(Thread Stack Size): Sets the memory size for individual thread stacks (e.g.,-Xss1M). Increase this only if you rely on deep recursion and faceStackOverflowErrors. -
-XX:MaxMetaspaceSize=<size>: Caps the amount of native memory used for storing class metadata. (Replaced the old PermGen space in Java 8).
2. Garbage Collection (GC) Flags
Different applications require different GC algorithms. A web server prioritizes low latency (short pauses), while a batch job prioritizes raw throughput.
Selecting the GC Algorithm
-XX:+UseG1GC(G1 Garbage Collector): The default in modern Java (9+). Excellent balance between throughput and low latency. Best for heaps larger than 2GB.-XX:+UseZGC(Z Garbage Collector): An ultra-low latency collector introduced in newer Java versions (11/15+). Pauses are sub-millisecond, regardless of heap size. Great for high-frequency trading or massive heaps.-XX:+UseParallelGC: Focuses entirely on throughput by using all CPU cores for GC. Pauses will be longer, but overall processing speed is higher. Best for batch processing.
Tuning the GC
-XX:MaxGCPauseMillis=<N>: Provides a "hint" to the G1GC algorithm on how long you want GC pauses to be (e.g.,-XX:MaxGCPauseMillis=200). The JVM will try to adjust heap sizing and collection frequency to meet this target.
3. Diagnostic and Debugging Flags
When things go wrong in production, these flags are your lifeline.
-
-XX:+HeapDumpOnOutOfMemoryError: This is arguably the most important flag in Java. If the JVM crashes due to OOM, it automatically writes a.hproffile to disk containing a snapshot of the entire heap. You can open this file in tools like Eclipse MAT or VisualVM to find exactly which objects caused the memory leak. -
-XX:HeapDumpPath=<path>: Specifies the directory where the heap dump should be saved. -
-Xlog:gc*(Unified JVM Logging - Java 9+): Replaces older flags like-XX:+PrintGCDetails. Logs detailed information about every garbage collection event, which is crucial for GC tuning. Example:-Xlog:gc*=info:file=gc.log:time,uptime,level,tags -
-XX:+PrintCompilation: Logs information about methods as they are optimized by the JIT Compiler, helping you identify "hot" methods.
Example: A Production Startup Script
Here is an example of what a robust startup command might look like for a microservice running on a server with 8GB of RAM:
java -server \
-Xms4G \
-Xmx4G \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/logs/dumps/ \
-Xlog:gc*=info:file=/var/logs/gc.log:time,tags \
-jar my-application.jar
Common Pitfalls
1. Over-Tuning
The Mistake: Copying a massive block of 20 different -XX flags from a 10-year-old StackOverflow post.
The Fix: The modern JVM is incredibly smart. Start with just -Xms, -Xmx, and basic GC logging. Only add advanced tuning flags if profiling proves you have a specific performance bottleneck.
2. Container Awareness (Docker/Kubernetes)
The Mistake: Setting -Xmx8G inside a Docker container that is only allocated 4GB of RAM by Kubernetes. The Linux OOM Killer will instantly terminate your container.
The Fix: Since Java 10, the JVM is container-aware. Use -XX:MaxRAMPercentage=75.0 instead of hardcoding -Xmx. This tells the JVM to use 75% of the memory limit defined by the Docker container, allowing the container size to change without requiring you to update the JVM flags.
Summary
- JVM Flags allow you to tailor the JVM's memory management and execution profile to your specific application architecture.
- Always set
-Xmsand-Xmxto the same value in production to avoid resizing overhead. - Select the right Garbage Collector (
UseG1GC,UseZGC,UseParallelGC) based on whether you need low latency or high throughput. - NEVER deploy to production without
-XX:+HeapDumpOnOutOfMemoryError.