Java Series #0: Class loaders & JVM
Class Loading: Getting Ready to Run using JVM

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:
ClassNotFoundExceptionNoClassDefFoundErrorLinkageError
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:
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++.
Extension (Platform) ClassLoader
Loads JARs from the
$JAVA_HOME/lib/extdirectory.Example: JavaFX or other standard extensions.
Application (System) ClassLoader
Loads classes from your application’s
classpath.This is the one you indirectly use every day.
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:
It first delegates to its parent.
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
| Method | Description |
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
| Component | Role | Analogy |
| Interpreter | Reads bytecode instructions one by one and executes them directly. | Like a translator who reads and explains each line slowly. |
| JIT (Just-In-Time) Compiler | Compiles 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 Stack | Stores method calls and local variables for each thread. | Like separate notebooks for each student (thread). |
How Execution Happens (Step-by-Step)
Bytecode Loaded by the ClassLoader.
Verifier ensures safety (no memory violations).
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.
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.




