Core Syntax - JVM, JDK, JRE Mental Model
Understand how Java bytecode works, class loading basics, and the difference between compilation and runtime.
Why this step matters
Most beginner confusion in Java comes from mixing up tools and runtime concepts. If you understand the roles of JDK/JRE/JVM, bytecode flow, and class loading, debugging becomes much easier.
JVM, JDK, JRE: mental model
Use this simple separation:
| Term | Main role | What it contains |
|---|---|---|
| JVM | Executes Java bytecode | Class loader, JIT compiler, GC, runtime engine |
| JRE | Runtime environment for running apps | JVM + runtime libraries |
| JDK | Development kit for building apps | JRE + compiler (javac) + dev tools (javadoc, jar, etc.) |
Important modern note
In modern Java distributions (Java 11+), teams usually install a full JDK everywhere (local, staging, often production). Standalone JRE usage is now less common than before.
How bytecode works
Java source code (.java) is compiled into bytecode (.class), then executed by the JVM.
Flow:
- Write source code
- Compile with
javac - Run with
java(JVM)
Example
public class Main {
public static void main(String[] args) {
System.out.println("Hello JVM");
}
}
Compile and run:
javac Main.java
java Main
Inspect generated bytecode:
javap -c Main
The JVM executes instructions from bytecode, then optimizes hot paths with JIT compilation at runtime.
Class loading basics
Before code runs, classes must be loaded and prepared.
Class loader hierarchy (simplified)
- Bootstrap ClassLoader: core JDK classes (
java.lang.*, etc.) - Platform ClassLoader: platform libraries
- Application ClassLoader: your app classes from classpath/modulepath
Class lifecycle phases
- Loading: locate and read class bytecode
- Linking:
- Verification (bytecode checks)
- Preparation (allocate static fields)
- Resolution (bind symbolic references)
- Initialization: execute static initializers and assign static values
Understanding this helps explain errors like ClassNotFoundException or NoClassDefFoundError.
Compilation vs runtime
These are different failure domains.
Compilation time (with javac)
Detected before execution:
- syntax errors
- type mismatch
- missing symbols
Runtime (inside JVM when executing)
Detected while running:
NullPointerExceptionArithmeticException- classpath/class loading issues
- performance and memory issues
You can pass compilation and still fail at runtime.
Quick practical checklist
- Use one JDK version across local, staging, and production
- Confirm with:
java -version
javac -version
- Keep classpath/modulepath explicit in scripts
- Distinguish compile errors from runtime failures in logs
Common mistakes to avoid
- Installing only a runtime when you need compiler tools
- Mixing different Java versions between IDE and terminal
- Assuming class loading is flat (it is hierarchical)
- Treating runtime exceptions as “compiler issues”
Takeaway
At a high level:
- JDK builds your code
- JVM runs your code
- Bytecode is the bridge
- Class loading controls availability at runtime
Once this model is clear, the next Java topics become much more straightforward.