JavaHow Java Works

How Java Works

When you run a Java program, three distinct pieces of software cooperate behind the scenes: the compiler, the class file format (bytecode), and the Java Virtual Machine. Understanding this pipeline demystifies both how Java achieves “Write Once, Run Anywhere” and why some Java concepts (like the JVM warming up) behave differently from a purely native-compiled language.

The Three-Step Pipeline

Step

Tool

Input

Output

  1. Compile

javac (the Java compiler)

Source code (.java files)

Bytecode (.class files)

  1. Load & Verify

The JVM's class loader

Bytecode (.class files)

Verified, loaded classes in memory

  1. Execute

The JVM's execution engine

Loaded bytecode

Native machine instructions, run by the CPU

Step 1 — Compiling to Bytecode

The compiler, javac, reads your .java source files and checks them for syntax errors and type errors. If everything checks out, it produces one .class file per class, containing bytecode — a compact, platform-neutral set of instructions. Bytecode is not native machine code for any particular CPU; it is an intermediate representation designed specifically to be portable.

Step 2 — Loading and Verifying

When you run a program, the JVM’s class loader reads the relevant .class files into memory and runs a bytecode verifier over them. This verifier checks that the bytecode doesn’t do anything unsafe — like accessing memory it shouldn’t or violating type rules — before a single instruction executes. This is a key part of Java’s security model.

Step 3 — Executing on the JVM

Finally, the JVM executes the bytecode. Early on, it interprets bytecode instructions one at a time. For code that runs frequently — hot loops, hot methods — the JVM’s Just-In-Time (JIT) compiler kicks in and translates that bytecode into native machine instructions for the actual CPU it’s running on, caching the result so future calls run at native speed.

Why Two Steps Instead of One?

A traditional compiled language (like C) compiles source code directly into native machine code for one specific operating-system-and-CPU combination; that binary won’t run anywhere else without recompiling. Java splits compilation into two stages precisely to avoid that limitation:

  • The compiler (javac) only ever needs to know about bytecode — it doesn't need a different version for every operating system or CPU.

  • The JVM is the only piece that's platform-specific, and a JVM already exists for essentially every mainstream platform.

  • The same .class files run unmodified on any machine with a compatible JVM installed.

In short: the bytecode is universal, and the JVM is what adapts it to a specific machine at run time. That split is the entire mechanism behind “Write Once, Run Anywhere.”

JDK, JRE, and JVM
This page focuses on the compile-and-execute pipeline. If you want a deeper breakdown of exactly what the JDK, JRE, and JVM each provide and how they relate to one another, see the dedicated JDK vs JRE vs JVM page.
Tip
You can actually inspect bytecode yourself with the `javap` disassembler tool that ships with the JDK — running `javap -c HelloWorld.class` prints the raw bytecode instructions the compiler generated, which is a great way to build intuition for what’s really happening under the hood.