The JVM is the virtual machine that loads and executes Java bytecode. It is the piece that allows running the same compiled program on different platforms.
When you start with Java, the first thing you encounter is an alphabet soup: JVM, JRE, JDK. What do I install? What do I need just to run? And what about programming?
Let’s pop the hood on Java to understand its architecture and distinguish the responsibilities of the JVM, the JRE, and the JDK.
Understanding this separation helps diagnose errors like “javac is not recognized as an internal command” and choose what to install.
JDK, JRE and JVM
To understand the relationship between these three components, imagine them as a Russian doll (Matryoshka) or layers of an onion. One contains the other.
JVM: the virtual machine
It is the central piece. The JVM is an “imaginary machine” that lives inside your computer.
Your processor (Intel, AMD, ARM) understands specific instructions (machine code). Java does not speak directly to your processor. Java speaks to the JVM, and the JVM translates those commands to the processor in use.
- Its function: Load the code, verify it, and execute it.
- The detail: The JVM is platform-dependent. There is a specific JVM for Windows, one for Linux, and one for macOS.
JRE: the runtime environment
The JVM alone is not very useful; it is like an engine without wheels. It needs libraries to function (for example, to open files, connect to the internet, or draw on screen).
The JRE is the set of JVM + Standard Libraries of Java (the Java API).
- Who is it for? Conceptually, for someone who only needs to run an application. Since Java 11, a standalone JRE is no longer published within OpenJDK; modern applications usually include a reduced runtime or require installing a JDK.
JDK: the development kit
To develop, we use the JDK, which bundles the runtime and development tools.
The JDK contains the JRE (which in turn contains the JVM), and also adds development tools. The most important ones are:
javac: The compiler (converts your.javacode into.class).javadoc: Documentation generator.jdb: The debugger.
Quick summary:
- If you are a user: The application typically includes its own runtime.
- If you are a developer: You install the JDK.
The compilation process: bytecode
In languages like C++, when you compile, you generate an .exe file (on Windows) that contains binary code the processor understands directly. If you take that .exe to Linux, it won’t work.
In Java, the process is different. When you use the javac compiler on your Main.java file, you don’t get machine code. You get a Main.class file containing Bytecode.
What is bytecode?
Bytecode is an intermediate language. It is compact binary code not designed for a real processor, but to be understood by the JVM.
It is like writing a book in Esperanto. No one speaks it natively, but with a translator (the JVM) anyone can understand it.
Once you have the Bytecode, you can take it to any operating system. There, the installed JVM will read it and translate it into the native instructions of that machine.
Is Java interpreted or compiled? (JIT)
Here comes the interesting technical part. Historically, Java was said to be “slow” because it was interpreted.
Initially, the JVM read the Bytecode instruction by instruction and translated it on the fly. This was inefficient compared to C++. But this changed many years ago thanks to the JIT (Just-In-Time Compiler).
The JIT compiler
Modern JVMs (like HotSpot) are very smart. They work like this:
- Initial interpretation: The program starts by interpreting the Bytecode to begin quickly.
- Profiling: The JVM monitors which parts of the code are executed most often (the “hot spots”).
- Live compilation: When it detects that a method is used frequently, the JIT compiler compiles that piece of Bytecode into native machine code and stores it in memory.
- Optimization: From then on, subsequent calls can execute the optimized native version instead of interpreting the bytecode again.
This process can significantly improve the performance of long-running applications, as the JVM optimizes based on observed behavior during execution.