Skip to main content

Command Palette

Search for a command to run...

Java Series #0: Class loaders & JVM

Class Loading: Getting Ready to Run using JVM

Updated
•5 min read•View as Markdown
Java Series #0: Class loaders & JVM
A
I’m a Full-stack Developer who enjoys turning ideas into simple and useful experiences. I like building clean user interfaces, exploring AI, and understanding how things work behind the scenes. From creating websites to trying out new technologies, I’m always curious and learning something new. Next.js, Node.js, MongoDB, and Java Spring Boot are the tools I use regularly. I enjoy experimenting with projects, sharing what I learn, and improving with every build. Vibing + Thinking.

Why Care About ClassLoaders?

Ever wondered how Java magically finds and loads your classes before your main() even starts?
That’s where the ClassLoader subsystem comes in — it’s literally the first thing the JVM uses after starting up.
Understanding it helps you debug mysterious errors like:

  • ClassNotFoundException

  • NoClassDefFoundError

  • LinkageError

What Exactly Is a ClassLoader?

A ClassLoader is responsible for dynamically loading .class files into JVM memory when needed.
You don’t have to manually import them — the JVM handles it lazily (only when required).

ClassLoader Hierarchy

Think of it like a layered security system:

  1. Bootstrap ClassLoader (Primordial Loader)

    • Native part of JVM.

    • Loads core Java libraries (like java.lang, java.util, etc.).

    • Doesn’t exist as a Java object — it’s written in C/C++.

  2. Extension (Platform) ClassLoader

    • Loads JARs from the $JAVA_HOME/lib/ext directory.

    • Example: JavaFX or other standard extensions.

  3. Application (System) ClassLoader

    • Loads classes from your application’s classpath.

    • This is the one you indirectly use every day.

  4. Custom ClassLoaders (User-Defined)

    • For special use cases — e.g., reloading plugins, frameworks like Spring, or sandboxing apps.

Delegation Model (Parent-First)

When you ask a ClassLoader to load something:

  1. It first delegates to its parent.

  2. If the parent doesn’t find the class, only then it tries to load it itself.

👉 This prevents duplicate loading and keeps the core libraries safe from being overridden.

Visual analogy:

Imagine a teacher (AppClassLoader) asking the principal (ExtensionClassLoader), who asks the director (BootstrapClassLoader).
If no one knows the student (class), only then the teacher admits them.

Commonly Used Methods

MethodDescription
loadClass(String name)Loads the class if not already loaded
findClass(String name)Finds the class definition
getParent()Returns the parent loader
defineClass()Converts byte array to a Class object

Example: Custom ClassLoader

public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("Loading class: " + name);
        return super.findClass(name);
    }
}

You could use it like:

MyClassLoader loader = new MyClassLoader();
Class<?> c = loader.loadClass("com.example.MyClass");

This concept is often used by:

  • Web servers (like Tomcat)

  • Frameworks (like Spring Boot, OSGi)

  • IDEs and plugin systems


Execution Engine & JIT Compiler — The Brain of the JVM

What Is the Execution Engine?

Once the ClassLoader loads bytecode into memory and the Verifier checks it for safety,
the Execution Engine takes over — it’s the JVM’s core brain that actually runs your program.

Think of it like this:

ClassLoader brings the ingredients (classes),
the Execution Engine is the chef who cooks them into machine code your CPU understands.

Core Components of the Execution Engine

ComponentRoleAnalogy
InterpreterReads bytecode instructions one by one and executes them directly.Like a translator who reads and explains each line slowly.
JIT (Just-In-Time) CompilerCompiles frequently executed parts (hot code) into native machine code for speed.Like memorizing common phrases to speak faster.
Garbage Collector (GC)Frees up memory used by objects that are no longer reachable.Like an automatic cleanup crew.
Runtime Stack / Thread StackStores method calls and local variables for each thread.Like separate notebooks for each student (thread).

How Execution Happens (Step-by-Step)

  1. Bytecode Loaded by the ClassLoader.

  2. Verifier ensures safety (no memory violations).

  3. Execution Engine picks it up.

    • Initially, Interpreter starts executing line-by-line.

    • JVM monitors “hot” code (frequently run methods or loops).

    • When a method becomes “hot,” JIT Compiler converts it into native machine code for faster execution.

  4. Garbage Collector runs in the background to clean unused objects.

Just-In-Time (JIT) Compiler – The Secret to Java’s Speed

What It Does:

Instead of interpreting code every time, JIT compiles repeated bytecode segments into native code,
stores it in memory, and reuses it — this drastically speeds up execution.

Types of JIT Compilation:

  • Method-based JIT → Compiles entire methods when they’re called often.

  • Adaptive JIT → Compiles based on runtime profiling data.

  • Tiered Compilation (Modern JVMs) → Combines interpreter + JIT for balance between startup speed and performance.

Analogy:

The first time you explain something, it’s slow (interpretation).
Once you’ve repeated it 100 times, you can say it instantly (JIT-compiled).

Memory Interaction with Execution Engine

When the execution engine runs code, it interacts with:

  • Method Area (class metadata, method bytecode)

  • Heap (object instances)

  • Stack (method calls and local variables)

  • PC Register (points to current instruction)

  • Native Method Stack (for native library calls like C/C++ code)

Visual Overview

                ┌────────────────────────────┐
                │        Class Loader        │
                └────────────┬───────────────┘
                             │
                             ▼
                ┌────────────────────────────┐
                │     Bytecode Verifier      │
                └────────────┬───────────────┘
                             │
                             ▼
                ┌────────────────────────────┐
                │     Execution Engine       │
                │ ┌────────────────────────┐ │
                │ │ Interpreter            │ │
                │ │ Just-In-Time Compiler  │ │
                │ │ Garbage Collector      │ │
                │ └────────────────────────┘ │
                └────────────┬───────────────┘
                             │
                             ▼
                ┌────────────────────────────┐
                │        Runtime Data        │
                │     (Heap, Stack, etc.)    │
                └────────────────────────────┘

Real-World Analogy

Imagine a movie being dubbed live (interpreter).
After a few screenings, the dubbing is recorded and reused (JIT).
Meanwhile, unused footage is deleted (GC).
That’s how the JVM keeps performance and memory in perfect sync.


Refer to memory management in java blog on this series for better clarity on Runtime data.