βοΈ ClassLoader β The JVMβs Gatekeeper¶
A
ClassLoaderis the bridge between the bytecode world (.classfiles, JARs, modules) and the runtime world (objects and types).
It tells the JVM where to find classes and how to bring them into memory.
Every class in Java β from String to your own β is loaded by exactly one ClassLoader.
1. The Journey: From Source to Runtime¶
source (.java)
β compiled by javac
bytecode (.class)
β loaded by
ClassLoader
β defined in
Class<?> (Metaspace)
β instantiated into
Object (Heap)
- ClassLoader β reads raw bytecode.
- Class<?> β the runtime representation of that type.
- Object β the actual instance living on the heap.
2. The Loading Chain β Delegation Model¶
Javaβs loaders form a hierarchy β each loader delegates to its parent before loading itself.
BootstrapClassLoader
β
PlatformClassLoader (JDK APIs)
β
AppClassLoader (your code & libs)
β
Custom Loaders (plugins, frameworks)
This model prevents multiple copies of core classes like java.lang.Object.
Only the top-most loader (Bootstrap) defines them once for the entire JVM.
3. The Classpath β The Loaderβs Search Map¶
The classpath is not a directory β itβs a runtime search list that tells the Application ClassLoader where to find
.classand.jarfiles.
When you run a Java program:
java -cp "out/:libs/*" com.example.Main
````
or equivalently:
```bash
export CLASSPATH=out/:libs/*
java com.example.Main
the JVM passes that list to the AppClassLoader, which searches those paths in order.
How the Classpath Works¶
- Linux/macOS β entries separated by
: - Windows β entries separated by
; -
Each entry can be:
-
a directory (searched recursively by package path)
- a JAR file
- a wildcard (
*) β includes all JARs in a directory
Example:
You can inspect your runtime classpath with:
Classpath vs Module Path¶
| Aspect | Classpath (Traditional) | Module Path (Java 9+) |
|---|---|---|
| Used by | AppClassLoader | Module system (JPMS) |
| Structure | Flat list of dirs/JARs | Graph of named modules |
| Dependency visibility | All-to-all | Explicit requires / exports |
| Common usage | Spring, legacy apps, tools | Modular apps, JLink runtimes |
Even modular apps still rely on the ClassLoader mechanism under the hood β modules just add structure on top.
Quick Gotchas¶
- Wrong or missing classpath β
ClassNotFoundException java -jarignores-cpunless the JARβs manifest definesClass-Path- IDEs and build tools (Maven, Gradle, IntelliJ) construct the classpath automatically
Visual Summary¶
+----------------------+
| Classpath | β directories & JARs
+----------------------+
β
AppClassLoader
β
JVM Runtime
4. Built-in ClassLoaders¶
| Loader | Loads From | Access in Code | Example Classes |
|---|---|---|---|
| Bootstrap | Core JDK (rt.jar / modules) |
null (native) |
java.lang.*, java.util.* |
| Platform (Extension) | jre/lib/ext (old) or JDK modules |
ClassLoader.getPlatformClassLoader() |
java.sql.*, java.xml.* |
| Application (System) | CLASSPATH / modulepath |
ClassLoader.getSystemClassLoader() |
your app & dependencies |
| Custom | Anywhere you define | Subclass of ClassLoader |
plugins, agents, hot-reloaders |
Example:
ClassLoader app = ClassLoader.getSystemClassLoader();
System.out.println(app); // AppClassLoader
System.out.println(app.getParent()); // PlatformClassLoader
System.out.println(app.getParent().getParent()); // null (Bootstrap)
5. How Class Loading Works Internally¶
Each class passes through these phases:
- Loading β read bytes from
.class, JAR, or network. -
Linking β
-
Verification: bytecode safety checks.
- Preparation: allocate static fields.
- Resolution: replace symbolic refs with direct ones.
- Initialization β run static blocks and field initializers.
You can intercept loading by overriding findClass(name).
6. Relationship with Class<?>¶
Every loaded class has an associated Class<?> object that remembers its loader.
If two identical classes are loaded by different loaders β they are different types to the JVM:
Thatβs why app servers (Tomcat, Spring Boot) can isolate multiple apps with identical class names.
7. Writing a Custom ClassLoader¶
public class MyLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = Files.readAllBytes(Path.of(name.replace('.', '/') + ".class"));
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
}
Usage:
ClassLoader loader = new MyLoader();
Class<?> c = loader.loadClass("com.example.Hello");
Object instance = c.getDeclaredConstructor().newInstance();
8. loadClass() vs findClass()¶
| Method | Role |
|---|---|
loadClass(name) |
Handles delegation (calls parent first). |
findClass(name) |
Defines how your loader finds the class. |
If you override findClass(), always call super.loadClass(name, false) first to respect delegation.
9. Why Custom Loaders Exist¶
| Purpose | Example |
|---|---|
| Hot reloading | Spring DevTools, JRebel |
| Plugin isolation | Tomcat webapps, OSGi |
| Dynamic bytecode | Hibernate proxies, generated classes |
| Security / sandboxing | Applets, custom interpreters |
10. Metaspace: Where Classes Live¶
- Pre-Java 8: PermGen held class metadata.
- Since Java 8: Metaspace (native memory).
- Stores each loaded classβs structure, methods, constants.
-
Classes unload only when:
-
No live references remain, and
- Their loader is garbage collected.
Long-lived custom loaders can leak memory if they hold on to objects.
11. Loading From Different Sources¶
You can load classes from anywhere β filesystem, JARs, or network.
URL[] urls = { new URL("file:/path/to/lib.jar") };
URLClassLoader loader = new URLClassLoader(urls);
Class<?> c = loader.loadClass("com.lib.Tool");
Close it when done:
12. Debugging & Introspection¶
Check which loader loaded a class:
System.out.println(String.class.getClassLoader()); // null (Bootstrap)
System.out.println(User.class.getClassLoader()); // AppClassLoader
List all URLs in the system loader:
URLClassLoader cl = (URLClassLoader) ClassLoader.getSystemClassLoader();
for (URL url : cl.getURLs()) System.out.println(url);
Print the loader hierarchy:
static void printHierarchy(ClassLoader cl) {
while (cl != null) {
System.out.println(cl);
cl = cl.getParent();
}
}
13. Class Unloading Rules¶
A class can be unloaded only when:
- Its defining
ClassLoaderis unreachable. - No live instances of that class remain.
- No reflective references (
Class<?>) survive.
In long-lived servers, leaked loaders = leaked memory.
14. Real-World Hierarchies¶
Spring Boot Fat JAR:
Tomcat:
Each webapp runs in isolation β same class names, different loaders.
15. ClassLoader + Reflection¶
Frameworks often pair loaders with reflection:
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> clazz = cl.loadClass("com.example.ServiceImpl");
Annotation a = clazz.getAnnotation(Service.class);
The Thread Context ClassLoader lets frameworks load user classes without hardcoding paths.
16. Common Pitfalls¶
- Forgetting to close
URLClassLoaderβ file lock leaks (especially on Windows). - Holding references to plugin classes β prevents GC/unloading.
- Violating delegation β
LinkageError. - Duplicate classes from different loaders β
ClassCastException: X cannot be cast to X. - Misusing context loader β
ServiceLoaderfails to locate providers.
17. Modern Evolution¶
- Java Modules (JPMS) β adds explicit dependencies; built atop loaders.
- Instrumentation API β allows runtime class redefinition.
Lookup#defineClass()(Java 15+) β defines classes without subclassingClassLoader.
18. The Mental Map¶
[ClassLoader] β defines β [Class<?> in Metaspace]
β β
hierarchy used to create
β β
[App/Custom Loaders] [Objects on Heap]
| Concept | Lives In | Purpose |
|---|---|---|
ClassLoader |
Heap | Loads and defines classes |
Class<?> |
Metaspace | Metadata for a type |
Object |
Heap | Runtime instance |
Classpath |
JVM property | Search map for AppClassLoader |
Metaspace |
Native memory | Class metadata store |
19. Quick Reference¶
| Task | Code |
|---|---|
| System loader | ClassLoader.getSystemClassLoader() |
| Platform loader | ClassLoader.getPlatformClassLoader() |
| Loader of a class | MyClass.class.getClassLoader() |
| Get parent loader | getParent() |
| Define manually | defineClass(name, bytes, 0, len) |
| Close URL loader | close() |
| Get classpath | System.getProperty("java.class.path") |
20. Final Mind Model¶
ββββββββββββββ
β ClassLoaderββββββββ defines
βββββββ¬βββββββ β
β ββββββββββββββββ
ββββββββββΆβ Class<?> β (Metaspace)
ββββββ¬ββββββββββ
β instantiates
β
[Objects on Heap]
Everything that exists at runtime passes through this chain. The classpath feeds it. The ClassLoader enacts it. The JVM sustains it.
21. π Full Method Coverage¶
Below are the main methods grouped by purpose, including those not yet explicitly mentioned in your version.
1. Core Loading Methods¶
| Method | Description |
|---|---|
loadClass(String name) |
Main entry point. Performs parent-delegation before attempting to find the class. Usually not overridden. |
findClass(String name) |
Child loaderβs lookup logic. Usually overridden in custom loaders. |
defineClass(String name, byte[] b, int off, int len) |
Converts raw bytecode β Class<?>. Also checks security and defines it in the current loaderβs namespace. |
defineClass(String name, ByteBuffer b, ProtectionDomain pd) |
Same but accepts a ByteBuffer. |
resolveClass(Class<?> c) |
Links the class (resolves symbolic references). Often called after defining. |
findLoadedClass(String name) |
Checks whether the class has already been loaded by this loader. Useful to prevent re-definition. |
2. Resource Lookup (not classes, but files inside JARs / paths)¶
| Method | Description |
|---|---|
getResource(String name) |
Finds a single resource (delegates to parent first). |
getResources(String name) |
Returns all matches (Enumeration<URL>). |
getResourceAsStream(String name) |
Opens resource as stream. Convenient for configs inside JARs. |
findResource(String name) |
Childβs own lookup (skip parent). |
findResources(String name) |
Same idea, but for multiple results. |
getResourceAsStream(String name) |
Handy wrapper combining both. |
Note: These work for .properties, .xml, images, or anything packaged in classpath/JARs β not .class files specifically.
3. Classpath / URL Access (via subclasses)¶
| Method | Description |
|---|---|
getURLs() |
(only in URLClassLoader) β lists all JARs and directories this loader reads from. |
addURL(URL url) |
(protected in URLClassLoader) β dynamically extend classpath. |
close() |
(since Java 7) β release open JAR handles (critical on Windows). |
4. Context & Hierarchy Methods¶
| Method | Description |
|---|---|
getParent() |
Returns parent loader in the delegation chain. |
getName() |
Human-readable name of this loader (Java 9+). |
getDefinedPackage(String name) |
Returns metadata for a single package defined by this loader. |
getDefinedPackages() |
Returns all packages defined by this loader. |
setDefaultAssertionStatus(boolean enabled) |
Controls assertion checking. |
setPackageAssertionStatus(String pkg, boolean enabled) |
Per-package assertions. |
setClassAssertionStatus(String className, boolean enabled) |
Per-class assertions. |
clearAssertionStatus() |
Resets all assertion settings. |
These assertion methods were added when Java introduced assert β rarely used today, but still valid.
5. Security & ProtectionDomain¶
| Method | Description |
|---|---|
getDefinedPackage(String name) |
Returns info about the package and its signing/sealing metadata. |
definePackage(...) |
Defines a package with attributes (version, vendor, sealBase). |
getPackage(String name) |
Legacy method (pre-Java 9). Returns info if the package was already defined. |
getPackages() |
Legacy variant returning all loaded packages. |
getSystemResource(String name) / getSystemResourceAsStream(String name) |
Static utility methods using the system classloader. |
6. Thread Context Loader Utilities¶
| Method | Description |
|---|---|
Thread.currentThread().getContextClassLoader() |
Retrieves the loader used for reflection, frameworks, and service loading. |
Thread.currentThread().setContextClassLoader(loader) |
Temporarily changes loader for current thread. Used heavily by Spring, Hibernate, and ServiceLoader. |
Not part of ClassLoader class itself, but conceptually essential.
7. Internal / Advanced (less common but still relevant)¶
| Method | Description |
|---|---|
defineModule(...) |
Used by JPMS to define a Module for this loader. |
findModule(...) |
Looks up modules known by this loader. |
getUnnamedModule() |
Returns the βdefaultβ module (non-modular classes). |
registerAsParallelCapable() |
Declares that loader can safely load in parallel (multi-threaded). |
isRegisteredAsParallelCapable() |
Checks the above flag. |
8. Deprecated / Historical¶
| Method | Notes |
|---|---|
getSystemClassLoader() |
Still valid, but sometimes replaced by ModuleLayer.boot().findLoader(...) in modular apps. |
getSystemResource*() |
Static variants still used widely. |
defineClass variants with ProtectionDomain and CodeSource β older security model, still functional. |
Meta-Insight¶
ClassLoader is deceptively deep.
At first glance it looks like just a βread bytes and define classesβ utility, but in modern JVMs it also manages:
- Module boundaries (post-Java 9)
- Parallel class loading safety
- Resource abstraction across JARs and layers
- Assertion control and package sealing
The class is half relic, half skeleton key β much of the JVMβs modular world still stands on its shoulders.