Java Notes
Step-by-step explanation of how Java source code becomes executable — from writing .java files through javac compilation, bytecode generation, class loading, verification, and JVM execution.
Java has a unique two-stage compilation process that sets it apart from both purely compiled languages (C/C++) and purely interpreted languages (Python/JavaScript). This process is the key to Java's "Write Once, Run Anywhere" promise.
Overview: From Source to Execution
| .java | ───────────→ | .class | ───────────→ | Output |
|---|---|---|---|---|
| Source | (compile) | Bytecode | (execute) | Result |
Let's trace this entire journey with a real example:
// File: Greeting.java
public class Greeting {
private String name;
public Greeting(String name) {
this.name = name;
}
public String getMessage() {
return "Hello, " + name + "! Welcome to Java.";
}
public static void main(String[] args) {
Greeting g = new Greeting("Developer");
System.out.println(g.getMessage());
}
}Hello, Developer! Welcome to Java.
Stage 1: Writing Source Code (.java)
The process begins with a .java source file:
- Written in human-readable text
- Must follow Java syntax rules
- Filename must match the public class name
- Uses Unicode encoding (supports any language in comments/strings)
Stage 2: Compilation (javac)
The javac compiler transforms source code into bytecode:
What javac Does Internally
The compiler performs multiple phases:
| Phase | What Happens |
|---|---|
| 1. Lexical Analysis | Breaks code into tokens (keywords, identifiers, operators) |
| 2. Parsing | Builds an Abstract Syntax Tree (AST) |
| 3. Semantic Analysis | Type checking, name resolution, access control |
| 4. Code Generation | Generates bytecode instructions |
| 5. Output | Writes .class file |
Compilation Errors
The compiler catches errors at this stage:
public class CompileErrors {
public static void main(String[] args) {
// ERROR 1: Type mismatch
// int x = "hello"; // incompatible types: String cannot be converted to int
// ERROR 2: Undeclared variable
// System.out.println(y); // cannot find symbol: variable y
// ERROR 3: Missing semicolon
// int a = 5 // ';' expected
// ERROR 4: Access violation
// String.value; // value has private access in String
// ERROR 5: Checked exception not handled
// FileReader fr = new FileReader("test.txt");
// unreported exception FileNotFoundException
System.out.println("All errors are caught at compile time!");
}
}Compiler Options
Stage 3: Bytecode (.class file)
The output of compilation is a .class file containing bytecode — an intermediate representation that's neither source code nor machine code.
Understanding Bytecode
// Simple class to examine bytecode:
public class BytecodeExample {
public static int add(int a, int b) {
int sum = a + b;
return sum;
}
}// Use: javap -c BytecodeExample.class to see bytecode:
public static int add(int, int);
Code:
0: iload_0 // Load first parameter (a) onto operand stack
1: iload_1 // Load second parameter (b) onto operand stack
2: iadd // Pop both values, add them, push result
3: istore_2 // Store result in local variable 2 (sum)
4: iload_2 // Load sum back onto stack
5: ireturn // Return int value from stackClass File Structure
Every .class file has this structure:
public class ClassFileDemo {
public static void main(String[] args) {
// Verify the magic number and version
System.out.println("Class file starts with: 0xCAFEBABE");
// The class file format version
// Java 8 = 52, Java 11 = 55, Java 17 = 61, Java 21 = 65
String version = System.getProperty("java.class.version");
System.out.println("Class file version: " + version);
System.out.println("Java version: " + System.getProperty("java.version"));
}
}Class file starts with: 0xCAFEBABE Class file version: 65.0 Java version: 21.0.1
Why Bytecode (Not Native Code)?
| Approach | Advantage | Disadvantage |
|---|---|---|
| Compile to native (C/C++) | Maximum speed | Platform-specific, recompile for each OS |
| Interpret source (Python) | Cross-platform | Slow execution |
| Bytecode (Java) | Cross-platform + near-native speed | Needs JVM, startup overhead |
Stage 4: Class Loading
When you run java MathUtils, the JVM's Class Loader finds and loads the class:
public class ClassLoadingDemo {
// Static initializer — runs when class is loaded
static {
System.out.println("1. ClassLoadingDemo class loaded!");
}
public static void main(String[] args) {
System.out.println("2. Main method executing");
// Force loading of another class
System.out.println("3. About to use Helper class...");
Helper.doSomething(); // Helper class loaded NOW (lazy loading)
}
}
class Helper {
static {
System.out.println(" -> Helper class loaded!");
}
static void doSomething() {
System.out.println("4. Helper doing something");
}
}1. ClassLoadingDemo class loaded! 2. Main method executing 3. About to use Helper class... -> Helper class loaded! 4. Helper doing something
Key insight: Classes are loaded lazily — only when first referenced!
Stage 5: Bytecode Verification
Before execution, the JVM verifies the bytecode is safe:
public class VerificationDemo {
public static void main(String[] args) {
// The verifier checks:
System.out.println("Bytecode verification ensures:");
System.out.println("✓ Stack doesn't overflow or underflow");
System.out.println("✓ All local variable accesses are valid");
System.out.println("✓ Types are correct for all operations");
System.out.println("✓ Method calls match signatures");
System.out.println("✓ Object fields are accessed legally");
System.out.println("✓ Return types match method declarations");
System.out.println("✓ No illegal type casts");
// This is WHY Java is secure — malicious bytecode is rejected
// before it can execute
}
}Bytecode verification ensures: ✓ Stack doesn't overflow or underflow ✓ All local variable accesses are valid ✓ Types are correct for all operations ✓ Method calls match signatures ✓ Object fields are accessed legally ✓ Return types match method declarations ✓ No illegal type casts
Stage 6: Execution (Interpreter + JIT)
The JVM executes bytecode using a combination of interpretation and JIT compilation:
=== Cold Start (Interpreted) === Time: 1250 μs === After Warmup (JIT Compiled) === Time: 45 μs (Much faster after JIT compilation!)
JIT Compilation Tiers
Modern HotSpot JVM uses tiered compilation:
| Tier | Level | Description |
|---|---|---|
| Tier 0 | Interpreter | Line-by-line execution, profiling |
| Tier 1 | C1 (Client) | Quick compilation, basic optimizations |
| Tier 2 | C1 + profiling | With invocation counters |
| Tier 3 | C1 + full profiling | Detailed profiling data |
| Tier 4 | C2 (Server) | Aggressive optimization, maximum speed |
JIT Optimizations
Result: 7 Sum: 3
Complete Compilation-to-Execution Flowchart
| Developer writes | Calculator.java |
| │ javac │ Phase 1 | COMPILATION |
| ▼ Creates | Calculator.class (bytecode) |
| │ java │ Phase 2 | EXECUTION |
| │ 1. Class Load │ | Find and load .class |
| │ 2. Verify │ | Check bytecode safety |
| │ 3. Prepare │ | Allocate static memory |
| │ 4. Resolve │ | Link references |
| │ 5. Initialize │ | Run static blocks |
| │ 6. Execute │ | Interpret/JIT main() |
Comparing with Other Languages
=== Compilation Approaches === C/C++ (AOT Compiled): source.c → gcc → native binary (platform-specific) + Fastest execution - Recompile for each platform Python (Interpreted): script.py → python interpreter → execution + No compilation step, cross-platform - Slowest execution Java (Hybrid: AOT + JIT): Source.java → javac → bytecode → JVM (JIT) → native + Cross-platform + near-native speed - Startup overhead, memory usage
Single-File Execution (Java 11+)
Starting with Java 11, you can skip explicit compilation for single-file programs:
// Direct execution (Java 11+):
// $ java HelloDirect.java
// (javac runs internally, class file is not saved to disk)
public class HelloDirect {
public static void main(String[] args) {
System.out.println("No separate compilation needed!");
System.out.println("Java 11+ compiles and runs in one step");
System.out.println("Great for scripts and quick testing");
}
}// Run directly: java HelloDirect.java No separate compilation needed! Java 11+ compiles and runs in one step Great for scripts and quick testing
Common Mistakes
- Thinking .java files run directly — Java source code MUST be compiled to bytecode first (either explicitly with
javacor implicitly withjavafor single files in Java 11+). - Confusing compiler errors with runtime errors — Compiler errors (syntax, types) prevent
.classfile generation. Runtime errors (NullPointerException, ArrayIndexOutOfBounds) happen during execution. - Not understanding that bytecode ≠ machine code — Bytecode is a platform-independent intermediate format. It's NOT executable by the CPU directly — the JVM must interpret or JIT-compile it.
- Thinking compilation happens once — There are TWO compilations:
javaccompiles source to bytecode (ahead-of-time), and JIT compiles bytecode to native code (at runtime). - Ignoring the classpath — When your code uses external libraries, the compiler AND the JVM need to know where to find them (
-cporCLASSPATH). - Forgetting class names are case-sensitive —
java Myclasswon't findMyClass.class. The case must match exactly.
Best Practices
- Use an IDE — Modern IDEs (IntelliJ, Eclipse) compile continuously as you type, showing errors instantly
- Understand compilation errors — Read the FIRST error carefully; later errors often cascade from it
- Use
-Xlint:all— Enable all compiler warnings to catch potential issues early - Target the right Java version — Use
--releaseflag to ensure compatibility with production JVM - Profile JIT behavior — Use
-XX:+PrintCompilationto see what the JIT compiler is doing
Interview Questions
Q1: Explain the Java compilation and execution process step by step.
Answer: (1) Developer writes .java source file. (2) javac compiler performs lexical analysis, parsing, type checking, and generates .class bytecode. (3) JVM's ClassLoader loads the .class file. (4) Bytecode Verifier checks the bytecode for safety and correctness. (5) Execution Engine interprets the bytecode or JIT-compiles hot code paths to native machine code for faster execution.
Q2: What is the difference between compiler errors and runtime errors?
Answer: Compiler errors are detected by javac during compilation — they prevent the .class file from being generated (syntax errors, type mismatches, missing imports). Runtime errors occur during program execution — the code is valid syntactically but encounters problems at runtime (NullPointerException, division by zero, ArrayIndexOutOfBoundsException).
Q3: What is bytecode and why is it important?
Answer: Bytecode is a platform-independent intermediate instruction set stored in .class files. It's important because it enables Java's "Write Once, Run Anywhere" — the same bytecode runs on any platform with a JVM. It's more compact than source code and faster to interpret than source, but still portable because it doesn't contain platform-specific instructions.
Q4: What is the role of JIT compiler in Java?
Answer: The JIT (Just-In-Time) compiler identifies "hot" methods (frequently executed code) and compiles their bytecode into optimized native machine code at runtime. This combines the portability of bytecode with near-native execution speed. JIT also performs advanced optimizations like method inlining, loop unrolling, dead code elimination, and escape analysis that aren't possible with ahead-of-time compilation.
Q5: Can Java code be compiled to native executables?
Answer: Yes, using GraalVM Native Image, Java code can be compiled ahead-of-time (AOT) into platform-specific native executables. This eliminates the JVM, provides instant startup time, and reduces memory usage. However, it has limitations (no runtime class loading, limited reflection). Standard Java still uses the JVM approach for maximum flexibility.
Summary
Java's compilation process is a carefully designed two-stage pipeline: javac compiles human-readable source code into platform-independent bytecode, and the JVM's execution engine (interpreter + JIT compiler) converts that bytecode into highly optimized native machine code at runtime. This hybrid approach gives Java both platform independence and high performance — the best of both compiled and interpreted worlds.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Java Compilation Process.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, fundamentals, introduction, compilation
Related Java Master Course Topics