Mastering HotSpot JVM: Tuning JIT, GC, and Options for Optimal Performance
This article explains the architecture of the OpenJDK HotSpot JVM, covering bytecode execution, JIT compilation, garbage collection, ergonomic defaults, configuration options, their lifecycle, and practical tuning advice to help developers optimize Java applications for various environments.
1. Introduction
In this article you will learn about the system aspects of the OpenJDK HotSpot Java Virtual Machine (HotSpot JVM) and how to tune them for optimal performance in your programs and runtime environment.
HotSpot JVM is a remarkable and flexible technology. It is available as a binary for every major operating system and CPU architecture, from tiny Raspberry Pi Zero devices to large servers with hundreds of CPU cores and terabytes of RAM. Because OpenJDK is open source, HotSpot can be compiled for virtually any system and fine‑tuned with options, switches, and flags.
Java source code is compiled into bytecode using the
javaccompiler, typically hidden by build tools such as Maven or Gradle. The resulting class files are executed by the HotSpot JVM on a virtual stack machine that understands up to 256 different instructions, each identified by an 8‑bit opcode.
The bytecode program is run by an interpreter that fetches each instruction, pushes its operands onto the stack, executes the instruction, removes the operands, and leaves the result on the stack.
This abstraction gives Java the "write once, run anywhere" portability advantage: a class file compiled on one architecture can run on a completely different architecture's HotSpot JVM.
If you think this abstraction sacrifices performance, you are right – that is precisely where HotSpot JVM switches, options, and flags become useful.
2. JIT (Just‑In‑Time) Compilation
How can programs written in a portable, high‑level language like Java challenge the performance of programs compiled from low‑level languages such as C into native code?
The answer is HotSpot's JIT compilation technology. It profiles program execution and selectively optimizes the parts deemed most beneficial, called "hot spots." The JIT compiles these hot spots into native code using knowledge of the underlying architecture.
HotSpot includes two JIT compilers: C1 (client compiler) and C2 (server compiler). C1 provides fast, simple optimizations, while C2 performs more advanced, costlier optimizations.
C1 offers quick, basic optimizations.
C2 provides heavyweight optimizations after extensive analysis.
Since JDK 8 the default mode is tiered compilation, which runs both compilers: C1 gives an early speed boost, and C2 gathers profiling data before applying advanced optimizations. The generated native code is stored in the code cache.
3. Garbage Collection (GC)
In addition to JIT, HotSpot provides productivity and performance features such as multithreading, automatic memory management, and selectable garbage‑collection strategies.
Objects are allocated in the heap. When they become unreachable, the garbage collector reclaims their memory.
4. Ergonomic Defaults
HotSpot includes an ergonomic process that, at startup, inspects the execution environment and chooses reasonable default values for GC strategy, heap size, and JIT compiler based on CPU core count and available RAM. Current defaults are:
GC: G1 GC
Initial heap: 1/64 of physical memory
Maximum heap: 1/4 of physical memory
JIT: tiered compilation using both C1 and C2
You can view the ergonomic defaults with:
<code>java -XX:+PrintFlagsFinal | grep ergonomic</code>On a machine with 32 GB RAM (JDK 11) the output shows an initial heap of about 512 MB (1/64) and a maximum heap of 8 GB (1/4).
5. Customization
If the defaults do not suit your application, HotSpot offers extensive configurability.
There are three main categories of options:
Standard : common startup options such as
-classpath.
-X : non‑standard options for generic JVM properties (e.g.,
-Xmxfor maximum heap size).
-XX : advanced options for fine‑grained JVM tuning.
6. -XX Options
-XX options are grouped by purpose:
Product : most commonly used options.
Experimental : enable with
-XX:+UnlockExperimentalVMOptions(e.g., ZGC in JDK 11).
Manageable : can be changed at runtime via MXBean or other tools.
Diagnostic : provide detailed VM diagnostics; enable with
-XX:+UnlockDiagnosticVMOptions.
Developmental : require a special debug build of HotSpot.
7. Added and Removed Options
Option lifecycles evolve with each JDK release. Notable changes include:
In JDK 9 many
-XX:+Print...and
-XX:+Trace...logging options were replaced by the unified
-Xlogsystem.
Experimental collectors (ZGC, Epsilon, Shenandoah) caused the option count to peak at 1504 in JDK 11.
The CMS collector was removed in JDK 14, reducing the total number of options.
Tables summarising options removed before OpenJDK 17 and new options added in OpenJDK 17 are shown as images.
8. Option Lifecycle Management
Since JDK 9, option removal follows a three‑step process: deprecated → obsolete → removed, with warnings issued at each stage.
For example, the
-XX:+AggressiveOptsoption was deprecated in JDK 11, became obsolete in JDK 12, and was finally removed in JDK 13.
9. Migrating to a Higher JDK Version
Tools such as JaCoLine can analyze your command‑line options and highlight incompatibilities when moving to a newer JDK.
10. JVM Parameter Tuning Recommendations
There is no one‑size‑fits‑all tuning guide, but the following options are commonly useful on JDK 11 and later:
Understand memory usage; GC pauses are the main cost of heap allocation.
Configure heap size with
-Xmxor
-XX:MaxRAMPercentage.
Set initial heap with
-Xmsor
-XX:InitialRAMPercentage.
Handle OutOfMemoryError with
-XX:+ExitOnOutOfMemoryError,
-XX:+HeapDumpOnOutOfMemoryError, and
-XX:HeapDumpPath.
Select a GC collector (G1GC is default; alternatives include Serial, Parallel, CMS (removed), and ZGC).
Adjust young‑generation sizes with
-XX:NewSizeand
-XX:MaxNewSize, and set promotion thresholds with
-XX:MaxTenuringThreshold.
Enable basic GC logging with
-Xlog:gcor detailed logging with
-Xlog:gc*.
Inspect JIT activity using
-XX:+PrintCompilationor detailed logs with
-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:LogFile=jit.log.
These settings help you understand heap allocation patterns, GC behavior, and JIT optimizations, allowing you to make informed performance decisions.
Translator's Note
Thanks to the contributors who translated and proofread this article.
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.