Back to Java Roadmap

Core Syntax - JVM, JDK, JRE Mental Model

Understand how Java bytecode works, class loading basics, and the difference between compilation and runtime.

#java #jvm #jdk #jre

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:

TermMain roleWhat it contains
JVMExecutes Java bytecodeClass loader, JIT compiler, GC, runtime engine
JRERuntime environment for running appsJVM + runtime libraries
JDKDevelopment kit for building appsJRE + 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:

  1. Write source code
  2. Compile with javac
  3. 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

  1. Loading: locate and read class bytecode
  2. Linking:
    • Verification (bytecode checks)
    • Preparation (allocate static fields)
    • Resolution (bind symbolic references)
  3. 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:

  • NullPointerException
  • ArithmeticException
  • 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.