Java Program Execution Flow
Reading Time: 7 Minutes
Difficulty: Beginner
Topic Summaryβ
When you write a Java program and click "Run," a lot of things happen behind the scenes. Java first compiles your code into bytecode, then the JVM interprets and runs that bytecode. Understanding this flow helps you debug errors, understand error messages, and appreciate what makes Java special.
What You'll Learnβ
- What happens at each stage of Java execution
- The role of
javac(compiler) and JVM (runtime) - What bytecode is and what a
.classfile contains - How JIT compilation speeds things up
- The difference between compile-time errors and runtime errors
Prerequisitesβ
- Lesson 003 β Features of Java (platform independence)
- Lesson 004 β JDK vs JRE vs JVM
Explanationβ
The Complete Execution Flowβ
ββββββββββββββββββββ
β You write code β
β HelloWorld.java β
β (Source Code) β
ββββββββββ¬ββββββββββ
β
β Step 1: javac compiler (part of JDK)
βΌ
ββββββββββββββββββββ
β Bytecode β
β HelloWorld.classβ
β (Platform β
β Independent) β
ββββββββββ¬ββββββββββ
β
β Step 2: JVM loads the .class file
βΌ
ββββββββββββββββββββββββββββββββββββββββ
β JVM β
β β
β ββββββββββββββ βββββββββββββββββββ β
β βClass Loaderββ βBytecode Verifierβ β
β ββββββββββββββ ββββββββββ¬βββββββββ β
β β β
β ββββββββββββββΌβββββββββ β
β β Execution Engine β β
β β β β
β β Interpreter β β
β β + β β
β β JIT Compiler β β
β ββββββββββββββ¬βββββββββ β
βββββββββββββββββββββββββββββΌβββββββββββ
β
βΌ
ββββββββββββββββββββββββ
β Native Machine Code β
β (Your CPU runs it!) β
ββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββ
β OUTPUT on Screen β
β "Hello, World!" β
ββββββββββββββββββββββββ
Step 1: Writing Source Code (.java file)β
You write Java code in a plain text file with the .java extension. This is called source code β code that humans can read.
// File: HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Rules for the file name: The file name MUST match the public class name. If your class is HelloWorld, the file must be HelloWorld.java.
Step 2: Compilation β javac (Java Compiler)β
When you run javac HelloWorld.java in the terminal, the javac compiler reads your source code and:
- Checks syntax β is the code grammatically correct?
- Checks types β are you using variables correctly?
- Converts source code into bytecode
- Produces a
HelloWorld.classfile
This is called a compile-time check. Errors caught here are called compile-time errors (or syntax errors).
Command: javac HelloWorld.java
Output: HelloWorld.class (bytecode file)
What is bytecode? Bytecode is a set of instructions written for an imaginary "ideal computer" (the JVM). It's not your CPU's native instructions β it's a middle format that any JVM can understand.
If you open a .class file in a text editor, you'll see something unreadable like:
cafΓ© babe 0000 003d 001d 0a00 0200 ...
(Java class files start with the magic bytes cafe babe β yes, that's actually a Java easter egg! β)
Step 3: Class Loadingβ
When you run java HelloWorld, the JVM's Class Loader takes over:
- Bootstrap Class Loader β loads core Java classes (java.lang, java.util, etc.)
- Extension Class Loader β loads extension libraries
- Application Class Loader β loads YOUR
HelloWorld.classfile
The class loader reads the .class file and loads it into memory (heap).
Step 4: Bytecode Verificationβ
Before running your code, the Bytecode Verifier checks:
- Is the bytecode properly formatted?
- Does the code try to do anything illegal (like accessing memory it shouldn't)?
- Are all types used correctly?
This is a security measure β it prevents malicious or corrupted bytecode from running.
Step 5: Execution β Interpreter + JIT Compilerβ
The Execution Engine runs your bytecode in two ways:
5a. Interpreterβ
The interpreter reads bytecode instructions one by one and executes them. Simple and works immediately, but slower for frequently-executed code.
5b. JIT (Just-In-Time) Compilerβ
The JVM monitors which code runs frequently ("hot code"). For hot code, the JIT compiler converts bytecode into native machine code that your CPU can run directly β much faster!
First few runs: Interpreted (slower but starts fast)
Hot code: JIT compiled to native code (fast!)
Result: Near-native performance over time
Step 6: Outputβ
After execution, the result appears β on screen, in a file, over a network, wherever your program sends it.
Compile-Time vs Runtime Errorsβ
| Compile-Time Error | Runtime Error | |
|---|---|---|
| When detected | During javac compilation | During program execution |
| Example | Missing semicolon, wrong type | Dividing by zero, null pointer |
| Caught by | Java compiler | JVM at runtime |
| Java term | Syntax error / type error | Exception |
Real-World Analogyβ
Think of this like making and playing a music album:
- Writing source code = A musician writes sheet music (
.javafile) - Compilation = Recording studio records to digital format (
.classbytecode) β can play on any player - Class Loading = Your music player loads the track into memory
- Bytecode Verification = Player checks the file isn't corrupted
- Interpretation/JIT = Player converts digital to sound waves your ears can hear (native machine code your CPU executes)
- Output = You hear the music π΅
The sheet music (source) β digital recording (bytecode) β sound (machine code) β music in your ears (output)!
Code Exampleβ
// File: ExecutionDemo.java
// Demonstrates Java execution flow concepts
public class ExecutionDemo {
public static void main(String[] args) {
System.out.println("=== Java Execution Flow Demo ===\n");
// Step 1: Variables loaded into memory by JVM
String message = "Hello from the JVM!";
int number = 42;
// Step 2: JVM executes these statements
System.out.println("Message: " + message);
System.out.println("Number: " + number);
// Step 3: JIT would optimize this loop (hot code!)
System.out.println("\nCounting (JIT optimizes loops like this):");
long sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
System.out.println("Sum of 1-10 = " + sum);
// Step 4: Runtime information from JVM
Runtime runtime = Runtime.getRuntime();
System.out.println("\n--- JVM Memory Info ---");
System.out.println("Total Memory: " + runtime.totalMemory() / 1024 + " KB");
System.out.println("Free Memory: " + runtime.freeMemory() / 1024 + " KB");
System.out.println("Processors: " + runtime.availableProcessors());
}
}
Outputβ
=== Java Execution Flow Demo ===
Message: Hello from the JVM!
Number: 42
Counting (JIT optimizes loops like this):
Sum of 1-10 = 55
--- JVM Memory Info ---
Total Memory: 256000 KB
Free Memory: 249856 KB
Processors: 8
Terminal Commandsβ
# Step 1: Compile your Java file
javac ExecutionDemo.java
# This creates: ExecutionDemo.class
# Step 2: Run the compiled program
java ExecutionDemo
# Note: Don't include .class extension when running!
# java ExecutionDemo β
CORRECT
# java ExecutionDemo.class β WRONG
Common Mistakesβ
- β Mistake: Running
java HelloWorld.class(with .class) β β Fix: Usejava HelloWorldβ the JVM knows to look for the.classfile. - β Mistake: File name doesn't match class name β β
Fix: If your class is
public class MyProgram, the file MUST beMyProgram.java. - β Mistake: Trying to run
.javafile directly β β Fix: Always compile first withjavac, then run withjava. (Exception: Java 11+ allowsjava HelloWorld.javafor single-file programs)
Best Practicesβ
- Always compile with
javac FileName.javabefore running - Use
java -verbose:class HelloWorldto see which classes are being loaded β great for debugging - For Java 11+, you can run single-file programs directly with
java HelloWorld.java(no separate compile step) - Keep one public class per file for clarity
Interview Questionsβ
Q: Explain the Java program execution flow.
A: 1) Write source code (.java), 2) Compile with javac β bytecode (.class), 3) JVM's class loader loads the .class file, 4) Bytecode verifier checks safety, 5) Execution engine runs it via interpreter + JIT compiler, 6) Output is produced.
Q: What is bytecode in Java?
A: Bytecode is the intermediate, platform-independent code that javac produces from Java source code. It's stored in .class files and can be executed by any JVM on any platform, enabling "Write Once, Run Anywhere."
Q: What is JIT compilation?
A: JIT (Just-In-Time) compilation is when the JVM identifies frequently-executed ("hot") bytecode and compiles it into native machine code at runtime for faster execution. This gives Java near-native performance.
Q: What is the difference between compile-time and runtime errors?
A: Compile-time errors (syntax errors, type mismatches) are caught by javac before the program runs. Runtime errors (exceptions like NullPointerException, ArrayIndexOutOfBoundsException) occur during execution and are handled by the JVM.
Quick Revisionβ
β Flow: .java β javac β .class (bytecode) β JVM β Output
β javac = compiler (part of JDK), converts source to bytecode
β Bytecode = platform-independent intermediate code in .class files
β JVM loads, verifies, and executes bytecode
β JIT = compiles hot code to native machine code at runtime
β Compile-time errors caught by compiler; runtime errors caught by JVM
Related Topicsβ
- JDK vs JRE vs JVM (Lesson 004)
- Installing Java (Lesson 006)
- Your First Java Program (Lesson 007)
Next Lessonβ
Lesson 006 β Installing Java (JDK 21)