Java code doesn't run on your CPU directly. It runs on the JVM, a virtual machine that turns bytecode into machine code. This post follows one file from source to running code.
JDK, JRE, JVM
- JVM (Java Virtual Machine): runs bytecode. Holds the class loader, interpreter, JIT compiler and garbage collector.
- JRE (Java Runtime Environment): JVM + the standard library (
java.lang,java.util, …). Enough to run Java. - JDK (Java Development Kit): JRE + developer tools (
javac,jdb,jlink). Needed to build Java.
Your app's own dependencies (Maven or Gradle jars) are not part of the JRE. They load from the classpath. Since Java 11, Oracle ships only the JDK; use jlink to build a small custom runtime.
From .java to Machine Code
- You write
Hello.java. javaccompiles it to bytecode inHello.class. Bytecode is the same on every OS.- The class loader loads the class, links it (verify, prepare, resolve) and initializes it.
- The interpreter runs the bytecode, one instruction at a time. Starts fast, runs slow.
- The JVM counts calls. When a method gets hot, the JIT (Just-In-Time) compiler turns it into native machine code. HotSpot uses two JITs: C1 compiles fast, C2 optimizes hard.
Call the method a few times and watch it move from the interpreter to the JIT:
- Hello.javasource
- javaccompile
- Hello.classbytecode
- Class loaderload, link, init
- Interpreterruns bytecode
- JITnative code
Press Call. The first calls run in the interpreter, one bytecode at a time.
This is why Java apps "warm up": the first requests are slow, then speed climbs as hot code gets compiled.
JVM Memory: 5 Areas
Not three. The JVM spec defines five runtime data areas:
| Area | Shared? | Holds |
|---|---|---|
| Heap | All threads | Objects and arrays |
| Method area (Metaspace) | All threads | Class metadata, static variables, constant pool |
| JVM stack | Per thread | One frame per method call: local variables, operand stack |
| PC register | Per thread | Address of the current instruction |
| Native method stack | Per thread | Frames for native (C/C++) methods |
Since Java 8, HotSpot keeps the method area in Metaspace, in native memory outside the heap. It replaced PermGen.
Garbage Collection
The heap is split by age, because most objects die young:
- Young generation (Eden + Survivor): new objects land in Eden. A minor GC frees the dead ones and ages the survivors.
- Old generation: objects that survive enough minor GCs get promoted here. Cleaned less often.
Click an area to see what it holds, then allocate objects and run a GC:
Shared by all threads
One per thread
Heap: Every object made with new. Split into Young (Eden + Survivor) and Old. The GC works here. Size it with -Xms and -Xmx.
referenced garbage. G1 (default since Java 9) splits the heap into regions and collects the ones with the most garbage first, to meet a pause-time goal.
There are more than two collectors:
| Collector | Best for |
|---|---|
| G1 | Default since Java 9. Balanced throughput and pauses |
| ZGC | Huge heaps, pauses around 1 ms |
| Shenandoah | Low pauses, like ZGC |
| Parallel | Maximum throughput, longer pauses are OK |
| Serial | Small heaps, single-CPU containers |
| Epsilon | Never frees memory. Tests and benchmarks only |
CMS was removed in Java 14. Pick one with a flag, for example -XX:+UseZGC.
Summary
.java→javac→.classbytecode → class loader → interpreter, then JIT for hot code. Memory: heap, Metaspace, stack, PC register, native stack. GC: G1 by default, ZGC for low pauses.