JAVA / GETTING STARTED
What the JVM, JDK and JRE each do
Tell apart the JVM, the JRE and the JDK, say which piece does each job, and check from the command line which of them your machine has.
What you will learn
- Decide from java -version and javac -version whether you have a JDK or a runtime
- Name the jobs the JVM does at run time: verify, JIT-compile, allocate, collect garbage
- Read UnsupportedClassVersionError as compiled-by-newer-JDK than the JVM running it
- Explain why one class file runs unchanged on Linux, macOS and Windows
Understanding What the JVM, JDK and JRE each do
The JVM is a specification of an abstract machine, and HotSpot, Eclipse OpenJ9 and others are implementations of it. It reads class files, a compact instruction set for a stack machine, and never sees your .java text, which is why Kotlin, Scala and Clojure compile to the same class files and run on the same engine. At run time it verifies incoming bytecode, places objects on a heap it garbage-collects, and starts by interpreting before a JIT compiler translates hot methods into instructions for whatever CPU it happens to be on. It also pins down semantics: int is 32-bit two's complement everywhere, double is IEEE 754, an out-of-range array index must throw, so the same class file produces the same values on a laptop and on a server.
Bytecode on its own cannot print anything. The first line most people write calls System.out.println, and that code is already compiled and shipped with the runtime, alongside String, ArrayList, the file and network APIs, and the native libraries behind them. A JRE is exactly that pairing, a JVM plus the standard class library: enough to run a program, with no compiler anywhere in it. OpenJDK stopped publishing a standalone JRE after Java 8, since Java 9 the library is split into modules and a right-sized runtime is assembled with jlink, so today "JRE" usually means a vendor's runtime-only package.
A JDK is a complete runtime plus the tools that create and inspect class files: javac, jar, javadoc, javap, jshell, jlink, jpackage, and diagnostics such as jcmd. That gives the nesting: anything a JRE can do a JDK can do too, which is why the practical advice is to install a JDK and stop thinking about the JRE at all. The one relationship that is not symmetric is version. A JVM loads class files up to its own class-file version, so a Java 21 JVM happily runs Java 8 class files, while a Java 11 JVM rejects everything javac 21 produced.
public class WhatEachPartDid {
public static void main(String[] args) {
int max = Integer.MAX_VALUE;
System.out.println(max);
System.out.println(max + 1); // 32-bit two's complement wrap, fixed by the JVM
System.out.println((int) -3.9); // narrowing truncates toward zero
System.out.println(0.1 + 0.2); // IEEE 754 double arithmetic
System.out.println("String came from module " + String.class.getModule().getName());
}
}
The JVM executes class files under fixed rules, a JRE is that JVM plus the standard library it needs, and a JDK is a runtime plus the tools that produce class files.
Worked examples
Ask the runtime what it is
Prints which JVM booted your program and proves programmatically whether a compiler is present.
import javax.tools.ToolProvider;
public class WhichJava {
public static void main(String[] args) {
System.out.println("runtime version: " + Runtime.version());
System.out.println("vm name: " + System.getProperty("java.vm.name"));
System.out.println("java.home: " + System.getProperty("java.home"));
System.out.println("compiler here: " + (ToolProvider.getSystemJavaCompiler() != null));
}
}
Example explained
Line 1Runtime.version() reports the runtime you are executing on, not the javac that produced the class file; on a badly set up machine those two disagree.
Line 2java.vm.name identifies the JVM implementation, and "Server VM" means HotSpot with its full tiered JIT rather than a cut-down variant.
Line 3java.home is the installation the launcher actually booted, which settles arguments about which of several installed JDKs is in use.
Line 4ToolProvider.getSystemJavaCompiler() returns null when the jdk.compiler module is missing, so a non-null result proves this is a JDK; the three lines above it will read differently on your machine.
javac allows it, the JVM refuses it
Shows two checks the compiler cannot make, which the JVM therefore has to perform while the program runs.
public class WhoChecks {
public static void main(String[] args) {
Object boxed = "text";
try {
Integer n = (Integer) boxed;
System.out.println(n);
} catch (ClassCastException e) {
System.out.println("cast rejected at run time: " + e.getMessage());
}
int[] pair = new int[2];
try {
pair[5] = 1;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("index checked at run time: " + e.getMessage());
}
}
}
Example explained
Line 1javac accepts (Integer) on an Object reference because the declared type permits that downcast; only the running JVM knows the object is really a String, so the check belongs to run time.
Line 2The parenthesised detail is the JVM naming where both classes came from: module java.base, loaded by the bootstrap loader, meaning the library built into the runtime rather than anything you wrote.
Line 3pair[5] = 1 compiles because array length is not part of an array's type, and the JVM bounds-checks every access, which is how the message can quote both the index and the length.
Line 4Neither failure crashes the process: throwing a defined exception for an illegal operation is a duty the JVM specification assigns to the VM.
Important notes
String.class.getModule() needs Java 9 or later; on Java 8 the same library classes lived in rt.jar and there were no modules, so that line will not compile there.
"JVM" names a specification, not one program: HotSpot ships in OpenJDK builds while Eclipse OpenJ9 and Azul's VMs run the same class files with different GC and JIT internals, so speed and memory use can differ even though results must not.
Common mistakes
Installing a runtime-only package such as default-jre or a vendor JRE download, then hitting "javac: command not found": there is no compiler in a runtime, so nothing you write can become a class file.
Treating the three names as synonyms and ending up with javac from one major version and java from another; the code compiles, then dies at startup with UnsupportedClassVersionError: class file version 65.0, this version of the Java Runtime only recognizes class file versions up to 55.0.
Picturing the JVM as a background virtual machine you boot and manage like a VirtualBox guest, so you go looking for a service to start; the java launcher creates a VM inside its own process and it vanishes when the program ends, which is why settings such as -Xmx are per-run flags.
Try it yourself
Change, predict, then run
In a browser editor, print Runtime.version().feature() and Math.abs(Integer.MIN_VALUE), predicting the second value before you run it and explaining it from the JVM's fixed 32-bit int rule. Then print ToolProvider.getSystemJavaCompiler() != null to find out whether that sandbox is a full JDK or just a runtime.
Open the Java workspaceCheck your understanding
On one machine javac -version prints 21 and java -version prints 11, and a freshly compiled class fails with UnsupportedClassVersionError: class file version 65.0, this version of the Java Runtime only recognizes class file versions up to 55.0. Which explanation fits?
- Class files are forward compatible, so the error must mean the file is corrupt and needs recompiling.
- Two installations are on the PATH: javac 21 wrote a version 65.0 class file that the Java 11 JVM refuses to load.
- The runtime is missing java.base, so the JVM cannot find the standard library classes the program uses.
- The JVM compiles the .java source itself, so the version of javac cannot be involved in this error.
Show answer
A JVM loads class files only up to its own class-file version: 55.0 is Java 11 and 65.0 is Java 21, so the older VM cannot read the newer compiler's output, and the fix is to run a Java 21 JVM or compile with --release 11. The first option is tempting because Java is famously compatible, but that compatibility runs one way only, new JVMs read old class files and not the reverse; a corrupt file would give a ClassFormatError, not a version number the JVM can quote back to you.