From Java Source to CPU!

Louhab AliLouhab Ali
4 min read

You write Java code (source code).

This is your .java file — human-readable text you write.

public class Hello {
    public static void main(String[] args) {
        int x = 5 + 3;
        System.out.println(x);
    }
}

This is just plain text. The CPU can’t run it directly.

JDK (Java Development Kit) :

A toolbox for developers — it includes:

1- javac : Java compiler.

2- JRE : Java Runtime Environment (to run programs).

3- Development tools : (debuggers, documentation generators).

Purpose here**:** You use javac to compile your code.

Compilation by javac :

When you run:

javac Hello.java

The compiler does NOT produce machine code directly.
Instead, it produces Java bytecode (.class file).

maybe you want to know how javac works internally no !

1- Lexical Analysis : Break code into tokens (public, class, int, etc.).

2- Parsing : Build an AST (Abstract Syntax Tree).
Example (simplified AST for int x = 5 + 3;):

Assignment
├── Variable: x
└── Addition
    ├── Number: 5
    └── Number: 3

3- Semantic Analysis : Check types, scope, etc.

4- Bytecode Generation : Convert AST into JVM bytecode.

Java Bytecode :

A low-level, platform-independent (wora) set of instructions for the JVM.

1- You write your Java code once.

2- You can run it on any computer or device that has a JVM (Java Virtual Machine), without changing the code.

Because Java code is compiled into bytecode, and the JVM on each platform knows how to run that bytecode on its own CPU.

Example bytecode for int x = 5 + 3;

iconst_5   // push 5
iconst_3   // push 3
iadd       // add them
istore_1   // store in local variable 1 (x)

It’s not CPU assembly — it’s for the JVM.

JRE (Java Runtime Environment) :

The environment that lets Java programs run.

It includes:

1- JVM (Java Virtual Machine)

2- Libraries (Java standard classes)

3- Supporting files

When you type java Hello , you’re running the JRE, which loads the JVM.

JVM (Java Virtual Machine) :

A virtual CPU that executes Java bytecode.

It:

1- Loads .class files

2- Verifies bytecode for safety

3- Executes it either by interpretation or via JIT compilation.

Execution without JIT (Interpretation) :

If the JVM ran purely in interpretation mode, it would:

1- Read one bytecode instruction at a time (e.g., iload_1).

2- Look up its meaning ( search what iload_1 means**)**.

3- Perform the operation.

Why this is slower !

Each bytecode execution involves:

1- Decoding: Finding out what the instruction means.

2- Dispatching: Jumping to the right function to execute it.

3- Executing: Running that function in native code.

This adds extra steps compared to already having native CPU instructions ready to go.

JIT (Just-In-Time Compiler) :

A runtime compiler inside the JVM.

When the JVM detects that a piece of code is "hot" (run many times), JIT compiles it into native assembly for your CPU , but the other code (the “cold code”) takes a different path, it stays as bytecode and runs in the JVM interpreter.

JVM interpreter :

The JVM interpreter is a built-in component of the Java Virtual Machine whose job is to read and execute Java bytecode instructions one-by-one without compiling them into native CPU code first.

1- It’s not a separate program — it’s part of the JVM itself.

2- Think of it as the “slow but always-ready” execution engine inside the JVM.

3- Every JVM has at least an interpreter; some also have a JIT compiler alongside it.

When you run Java code for the first time:

  1. The JVM loads the .class file into memory.

  2. The interpreter reads the bytecode instructions.

  3. For each instruction, it finds the matching pre-written native code routine in the JVM and executes it.

Example for bytecode :
1- Reads iconst_5 → calls native routine to push 5 to JVM stack.

2- Reads iconst_3 → pushes 3.

3- Reads iadd → pops top two numbers, adds them, pushes result.

Relation to JIT :

Interpreter runs all code at the start (fast startup, slower execution) , JIT takes hot code from the interpreter and compiles it into native assembly for faster execution , JVM switches between interpreter and JIT at runtime.

Why? Native assembly runs directly on your processor without translation overhead.

Native Assembly Output :

A human-readable version of machine code, closely tied to the CPU’s instruction set.

Example (simplified x86-64 output for int x = 5 + 3;):

mov eax, 5          ; put 5 in register eax
add eax, 3          ; add 3 to eax
mov [rbp-4], eax    ; store result in memory

At this point:

1- This is CPU-specific (different for Intel vs ARM).

2- The CPU runs this directly.

Diagram :

[ Assembly Code ] -- assembler --> [ Machine Code ] -- runs on --> [ CPU ].

The Full Way :

Your Code (.java)
↓ 
javac (JDK)
Tokens → AST → Bytecode (.class)
↓ 
java (JRE) JVM loads bytecode 
↓ 
Interpreter (slow, startup) 
↓ 
JIT compiles hot code → Assembly 
↓ 
CPU executes native code (fast)
0
Subscribe to my newsletter

Read articles from Louhab Ali directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Louhab Ali
Louhab Ali