1. Overview
The Java Virtual Machine (JVM) is a powerful engine that powers Java applications. It executes compiled .class files, manages memory, and boosts performance using the techniques like Just-in-Time (JIT) compilation and Garbage Collection.
The JVM is incredibly flexible. With the right arguments, we can tweak its behavior to boost performance, troubleshoot problems, and try out experimental features with ease. In this tutorial, we’ll explore the different argument prefixes that we can use to configure the JVM.
2. What Are JVM Arguments?
JVM Arguments are special command-line options that can change the behavior of the JVM. These parameters control things like memory settings, performance tuning, enabling debugging and monitoring, garbage collection configuration, and experimental features.
We can specify these arguments when we start the JVM, for example:
java -Xmx512m -Denv=prod -verbose:gc -XX:+UseG1GC -jar App.jar
In the above command, we’ve used multiple different prefixes. Each prefix tells the JVM what kind of configuration is being passed.
- -Xmx512m – sets the maximum heap size to 512 MB. (Non-standard option)
- -Denv=prod – defines a system property named env with value prod. (System Property)
- -verbose:gc – enables garbage collection logging. (Standard option)
- -XX:+UseG1GC – tells JVM to use the G1 Garbage Collector. (Advanced option)
Let’s explore each prefix in detail.
3. Different JVM Argument Prefixes
3.1. System Properties (-D)
System properties are typically used to configure JVM-specific parameters, such as file encoding, user directories, JVM version, and other Java-specific configurations.
We can define key-value pairs known as System properties by passing them using the -D command-line argument, like below:
java -Denv=prod -jar App.jar
In the -D argument prefix, the letter D stands for Define. Java didn’t use other letters to avoid confusion. It’s short, simple, and easy to remember. It tells us that we’re defining a property.
3.2. Standard Options (–)
Java standard options are documented settings that we can use with any JVM to control its basic behavior. All JVM implementations support the standard option. These options help us manage how the JVM launches and runs Java programs.
For example, we can use -classpath to set the classpath, and -version to check the JVM version:
java -version
It prints:
openjdk version "17.0.14" 2025-01-21 LTS
OpenJDK Runtime Environment Corretto-17.0.14.7.1 (build 17.0.14+7-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.14.7.1 (build 17.0.14+7-LTS, mixed mode, sharing)
We can run the java command in a terminal to see a list of standard options:
Usage: java [options] <mainclass> [args...]
(to execute a class)
or java [options] -jar <jarfile> [args...]
(to execute a jar file)
where options include:
-cp <class search path of directories and zip/jar files>
-classpath <class search path of directories and zip/jar files>
--class-path <class search path of directories and zip/jar files>
A : separated list of directories, JAR archives,
and ZIP archives to search for class files.
To specify an argument for a long option, you can use --<name>=<value> or
--<name> <value>.
3.3. Non-Standard Options (-X)
We use -X options to access non-standard JVM features that often control memory and debugging behavior. These options can vary across different JVM implementations like OpenJDK, HotSpot, or Microsoft’s JVM. For example, a JVM from Red Hat may support -X options different from Microsoft’s.
We can list all the non-standard options using the following command:
java -X
The output will depend on the JVM implementation:
-Xbatch disable background compilation
-Xbootclasspath/a:<directories and zip/jar files separated by :>
append to end of bootstrap class path
-Xcheck:jni perform additional checks for JNI functions
-Xcomp forces compilation of methods on first invocation
-Xdebug does nothing. Provided for backward compatibility.
[...]
These options are useful for general-purpose tuning, but we should use them with caution because they’re implementation-specific and subject to change without notice.
3.4. Advanced Options (-XX)
We can use advanced JVM options to enable low-level, often experimental features. These options start with -XX. We can categorize these options into two types: boolean options, which let us enable or disable specific features, and value options, which allow us to set custom values.
Some of these options remain stable across versions, but others can change, become deprecated, or even be removed in future JVM releases. Since not all JVM implementations support every advanced option, we must use them carefully and stay updated as the JVM evolves.
Let’s take a look at the command that uses advanced options to tune the JVM’s garbage collection behavior for better responsiveness:
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar App.jar
Here, we first instruct the JVM to use the G1 Garbage Collector, then ask it to aim for GC pauses under 200 ms. This is a soft goal, meaning the JVM will try to meet it, but doesn’t guarantee it.
4. JVM Execution Modes
Let’s take a look at different JVM execution modes we can use to control JIT compilation, which compiles Java bytecode into native machine code at runtime and optimizes it based on runtime profiling.
We’ll use a Benchmark.java program that fills a HashMap with 1 million key-value pairs and retrieves all values to ensure correctness and measure performance.
public class Benchmark {
private static final int NUM_ENTRIES = 1_000_000;
public static void main(String[] args) {
Benchmark benchmark = new Benchmark();
benchmark.run();
}
public void run() {
HashMap<Integer, String> map = new HashMap<>();
// Fill the HashMap
long startPut = System.nanoTime();
for (int i = 0; i < NUM_ENTRIES; i++) {
map.put(i, "Value" + i);
}
long endPut = System.nanoTime();
System.out.println("Time to put: " + (endPut - startPut) / 1_000_000 + " ms");
// Retrieve from the HashMap
long startGet = System.nanoTime();
for (int i = 0; i < NUM_ENTRIES; i++) {
String value = map.get(i);
if (value == null) {
System.err.println("Missing key: " + i);
}
}
long endGet = System.nanoTime();
System.out.println("Time to get: " + (endGet - startGet) / 1_000_000 + " ms");
}
}
4.1. -Xint – Interpreted Mode Only
The -Xint flag forces the JVM to interpret all bytecode instead of compiling it. This disables the JIT compilation entirely. As a result, executions slow down severely.
This flag helps us isolate issues related to JIT behavior during debugging and compare the performance impact of JIT compilation versus pure interpretation.
Let’s see the performance of interpreted execution mode:
java -server -showversion -Xint Benchmark
openjdk version "17.0.14" 2025-01-21 LTS
OpenJDK Runtime Environment Corretto-17.0.14.7.1 (build 17.0.14+7-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.14.7.1 (build 17.0.14+7-LTS, interpreted mode, sharing)
Time to put: 1532 ms
Time to get: 261 ms
4.2. -Xcomp – Compile-Only Mode
The -Xcomp flag forces the JVM to compile all methods at first use, rather than waiting to identify hot methods. This sounds nice because it completely avoids the slow interpreter.
However, it often causes slower startup due to aggressive compilation, though it may deliver long-term performance gains.
Let’s see the performance of the compile execution mode:
java -server -showversion -Xcomp Benchmark
openjdk version "17.0.14" 2025-01-21 LTS
OpenJDK Runtime Environment Corretto-17.0.14.7.1 (build 17.0.14+7-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.14.7.1 (build 17.0.14+7-LTS, compiled mode, sharing)
Time to put: 1167 ms
Time to get: 10 ms
4.3. -Xmixed – Default Mode
The mixed mode starts by interpreting the code and then compiles the hot methods using the JIT compiler. This approach helps us balance startup time with long-term performance, giving us the benefits of both worlds.
The recent version of HotSpot uses mixed mode by default, so we no longer need to specify this flag manually. Let’s take a look at its performance:
java -server -showversion Benchmark
openjdk version "17.0.14" 2025-01-21 LTS
OpenJDK Runtime Environment Corretto-17.0.14.7.1 (build 17.0.14+7-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.14.7.1 (build 17.0.14+7-LTS, mixed mode, sharing)
Time to put: 75 ms
Time to get: 12 ms
The output shows that the default mixed mode offers the best balance between startup time and runtime performance. Interpreted mode -Xint is significantly slower and mainly used for debugging or analysis. Compiled mode -Xcomp reduces the execution time for hot paths but comes with a higher startup cost.
4.4. -Xverify – Verification Control
When Java code is compiled, it’s turned into bytecode, which is run by the JVM. The JVM performs bytecode verification before execution to ensure that the code complies with the JVM specification, uses the stack correctly, accesses variables safely, and maintains proper control flow. This step helps prevent crashes and security risks, particularly when running untrusted or third-party code.
The -Xverify option allows us to control when and whether the bytecode verification happens. The -Xverify:all option is the default behavior. It verifies all classes when the JVM loads them. This ensures both safety and correctness during execution.
The -Xverify:none option skips class verification during loading. It may slightly improve the startup time, but it reduces safety and can be risky:
java -Xverify:none -cp app.jar com.baeldung.Main
We can use VisualVM and Java Management Extensions (JMX) for remote monitoring of Java applications.
5. Conclusion
In this tutorial, we explored various JVM argument prefixes. JVM execution modes also fall under non-standard argument prefixes. The mixed mode provides the most efficient and stable behavior for general application use. Using the right JVM arguments can enhance an application’s performance, improve stability, and simplify debugging.
The decision to set specific JVM arguments depends on several factors, including the complexity of the application and its performance requirements. Using commonly used JVM arguments can enhance our application’s performance and functionality.