Skip to content
hector@hcmc:~/blog/how-java-runs-jvm

How Java Runs: JDK, JRE, JVM, JIT and GC

$ cat ~/blog/how-java-runs-jvm.mdx

What happens between writing a .java file and running it: JDK vs JRE vs JVM, bytecode, the JIT compiler, the five JVM memory areas and the garbage collectors.

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

JDK ⊃ JRE ⊃ JVM
JDKjavac, jdb, jlink, jshell
JREStandard library: java.lang, java.util…
JVMClass loader, interpreter, JIT, GC
  • 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

  1. You write Hello.java.
  2. javac compiles it to bytecode in Hello.class. Bytecode is the same on every OS.
  3. The class loader loads the class, links it (verify, prepare, resolve) and initializes it.
  4. The interpreter runs the bytecode, one instruction at a time. Starts fast, runs slow.
  5. 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:

java Hellointerpreted
  1. Hello.javasource
  2. javaccompile
  3. Hello.classbytecode
  4. Class loaderload, link, init
  5. Interpreterruns bytecode
  6. 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:

AreaShared?Holds
HeapAll threadsObjects and arrays
Method area (Metaspace)All threadsClass metadata, static variables, constant pool
JVM stackPer threadOne frame per method call: local variables, operand stack
PC registerPer threadAddress of the current instruction
Native method stackPer threadFrames 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:

jvm.memory5 areas

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:

CollectorBest for
G1Default since Java 9. Balanced throughput and pauses
ZGCHuge heaps, pauses around 1 ms
ShenandoahLow pauses, like ZGC
ParallelMaximum throughput, longer pauses are OK
SerialSmall heaps, single-CPU containers
EpsilonNever frees memory. Tests and benchmarks only

CMS was removed in Java 14. Pick one with a flag, for example -XX:+UseZGC.

Summary

.java → javac → .class bytecode → class loader → interpreter, then JIT for hot code. Memory: heap, Metaspace, stack, PC register, native stack. GC: G1 by default, ZGC for low pauses.