Demystifying Java Class Loaders: From Bootstrap to Custom Implementations
This article explains the three built‑in Java class loaders, their initialization via the Launcher, the parent‑delegation model, the role of the context class loader, how to create custom loaders, and the key changes introduced in Java 9, providing code examples and diagrams for each concept.
Class Loaders
In the JVM there are three primary class loaders: the Bootstrap (or root) class loader, the Extension class loader, and the Application class loader. Each loader is responsible for loading classes from a specific location.
The Bootstrap loader is implemented in native C++ and loads core libraries such as rt.jar from the JAVA_HOME/lib directory. It cannot be referenced directly from Java code; passing null to a custom loader delegates to it.
The Extension loader ( sun.misc.Launcher$ExtClassLoader) loads JARs from JAVA_HOME/lib/ext or directories specified by the java.ext.dirs system property.
The Application loader ( sun.misc.Launcher$AppClassLoader) is returned by ClassLoader.getSystemClassLoader() and loads classes from the user‑specified classpath.
System.out.println("boot:" + System.getProperty("sun.boot.class.path"));
System.out.println("ext:" + System.getProperty("java.ext.dirs"));
System.out.println("app:" + System.getProperty("java.class.path"));Initialization of Class Loaders
Both the Extension and Application loaders are created by the sun.misc.Launcher class, which itself is loaded by the Bootstrap loader.
public Launcher() {
Launcher.ExtClassLoader var1;
try {
// initialize extension class loader
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// initialize application class loader with the extension loader as parent
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// set context class loader for the current thread
Thread.currentThread().setContextClassLoader(this.loader);
}Parent Delegation Model
When a class loader receives a request to load a class, it first delegates the request to its parent. Only if the parent cannot find the class does the child attempt to load it. This delegation is implemented via composition rather than inheritance.
These diagrams illustrate the sequence of delegation calls.
ClassLoader#loadClass Source
The abstract ClassLoader class provides a default implementation of loadClass. Subclasses can override findClass to define how bytecode is obtained, or override loadClass to break the parent‑delegation contract (not recommended).
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ignore
}
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
// statistics omitted
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}Why Use Parent Delegation
The model guarantees type safety for core Java classes. Since every Java application depends on java.lang.Object, loading this class through a single, trusted loader (the Bootstrap loader) prevents multiple incompatible versions from appearing.
Context Class Loader
Each class loader retains a reference to its parent, but sometimes a parent‑loaded class needs to access a child‑loaded class. The context class loader solves this, as demonstrated by the JDBC driver loading process.
// Load driver class
Class.forName("com.mysql.jdbc.Driver");
// Obtain a connection
Connection conn = DriverManager.getConnection(url, user, password);In DriverManager, the context class loader is retrieved as follows:
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = (caller != null) ? caller.getClassLoader() : null;
synchronized (DriverManager.class) {
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
// ... use callerCL to load the driver implementation
}The context class loader is set in the Launcher constructor shown earlier, effectively making it the Application class loader.
Custom Class Loader
To create a custom loader, extend java.lang.ClassLoader and override findClass(String name) to return a Class object from a byte array. Overriding loadClass is only necessary if you deliberately break the parent‑delegation contract.
public class ClassLoaderTest extends ClassLoader {
private String classPath;
public ClassLoaderTest(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
return defineClass(name, classData, 0, classData.length);
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try (InputStream is = new FileInputStream(path);
ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[2048];
int num;
while ((num = is.read(buffer)) != -1) {
stream.write(buffer, 0, num);
}
return stream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
}
public static void main(String[] args) {
String classPath = "/Users/zzs/my/article/projects/java-stream/src/main/java/";
ClassLoaderTest loader = new ClassLoaderTest(classPath);
try {
Class<?> obj = loader.loadClass("com.secbro2.classload.SubClass");
System.out.println(obj.newInstance().toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}Custom loaders are useful when the standard loaders cannot satisfy specific requirements, such as in OSGi modules or hot‑deployment scenarios.
Java 9 Class Loader Changes
Java 9 introduces a new platform class loader that sits between the Bootstrap and Application loaders. The Extension loader is now represented by this platform loader. The Bootstrap loader is still represented by null for backward compatibility.
Key differences:
The -Xbootclasspath and -Xbootclasspath/p options are removed; only -Xbootclasspath/a remains, stored in the jdk.boot.class.path.append system property.
Extension mechanisms are discontinued; the platform class loader now handles what used to be the Extension loader.
Modules such as java.base, java.logging, java.prefs, and java.desktop are loaded by the Bootstrap loader, while other modules are loaded by the platform or application loaders.
Conclusion
This article covered the Java 8 class‑loader architecture, the parent‑delegation model, the context class loader, how to implement a custom loader, and the major changes introduced in Java 9. Readers are encouraged to explore the Java 9 module system for deeper understanding.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Senior Brother's Insights
A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
