Java uses a hierarchical classloading system with three main classloaders that work in a parent-delegation model.
ClassLoader Hierarchy
Bootstrap ClassLoader (Root) ↑ Extension ClassLoader ↑ Application ClassLoader ↑ Custom ClassLoaders
1. Bootstrap ClassLoader
- Parent: None (root classloader)
- Implementation: Native implementation (not Java)
- Responsibility: Loads core Java classes
public class BootstrapClassLoaderDemo { public static void main(String[] args) { // Classes loaded by Bootstrap ClassLoader ClassLoader stringClassLoader = String.class.getClassLoader(); ClassLoader objectClassLoader = Object.class.getClassLoader(); System.out.println("String class ClassLoader: " + stringClassLoader); // null System.out.println("Object class ClassLoader: " + objectClassLoader); // null // Bootstrap classpath can be viewed using: String bootClassPath = System.getProperty("sun.boot.class.path"); System.out.println("Bootstrap Classpath: " + bootClassPath); } } Location: JRE/lib/rt.jar, JRE/lib/resources.jar, etc.
2. Extension ClassLoader
- Parent: Bootstrap ClassLoader
- Implementation:
sun.misc.Launcher$ExtClassLoader - Responsibility: Loads extension libraries
public class ExtensionClassLoaderDemo { public static void main(String[] args) { // Get Extension ClassLoader ClassLoader extensionClassLoader = ClassLoader.getSystemClassLoader().getParent(); System.out.println("Extension ClassLoader: " + extensionClassLoader); System.out.println("Extension ClassLoader parent: " + extensionClassLoader.getParent()); // Extension directory String extDirs = System.getProperty("java.ext.dirs"); System.out.println("Extension directories: " + extDirs); } } Location: JRE/lib/ext/ directory
3. Application ClassLoader
- Parent: Extension ClassLoader
- Implementation:
sun.misc.Launcher$AppClassLoader - Responsibility: Loads application classes from classpath
public class ApplicationClassLoaderDemo { public static void main(String[] args) { // Current class's ClassLoader (Application ClassLoader) ClassLoader appClassLoader = ApplicationClassLoaderDemo.class.getClassLoader(); ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println("Application ClassLoader: " + appClassLoader); System.out.println("System ClassLoader: " + systemClassLoader); System.out.println("Are they same? " + (appClassLoader == systemClassLoader)); // Parent hierarchy System.out.println("Application → " + appClassLoader); System.out.println("Parent → " + appClassLoader.getParent()); System.out.println("Parent's Parent → " + appClassLoader.getParent().getParent()); // Classpath String classPath = System.getProperty("java.class.path"); System.out.println("Classpath: " + classPath); } } Delegation Model in Action
public class ClassLoadingDemo { public static void main(String[] args) { // Demonstrate classloader hierarchy printClassLoaderHierarchy(String.class); // Bootstrap printClassLoaderHierarchy(javax.swing.JFrame.class); // Extension? printClassLoaderHierarchy(ClassLoadingDemo.class); // Application // Load class explicitly loadClassWithDetails("java.lang.String"); loadClassWithDetails("java.util.ArrayList"); loadClassWithDetails("com.mysql.jdbc.Driver"); // If in classpath } public static void printClassLoaderHierarchy(Class<?> clazz) { System.out.println("\nClass: " + clazz.getName()); ClassLoader loader = clazz.getClassLoader(); while (loader != null) { System.out.println(" ↳ " + loader); loader = loader.getParent(); } System.out.println(" ↳ Bootstrap ClassLoader"); } public static void loadClassWithDetails(String className) { try { System.out.println("\nLoading: " + className); Class<?> loadedClass = Class.forName(className); System.out.println("Loaded by: " + loadedClass.getClassLoader()); } catch (ClassNotFoundException e) { System.out.println("Class not found: " + className); } } } Custom ClassLoader Example
public class CustomClassLoader extends ClassLoader { private final String classPath; public CustomClassLoader(String classPath, ClassLoader parent) { super(parent); // Set parent classloader this.classPath = classPath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { // Convert class name to file path String path = name.replace('.', '/') + ".class"; File file = new File(classPath, path); if (!file.exists()) { throw new ClassNotFoundException("Class not found: " + name); } // Read class file bytes byte[] classBytes = Files.readAllBytes(file.toPath()); // Define the class return defineClass(name, classBytes, 0, classBytes.length); } catch (IOException e) { throw new ClassNotFoundException("Failed to load class: " + name, e); } } } // Using custom classloader public class CustomClassLoaderDemo { public static void main(String[] args) throws Exception { String customClassPath = "/path/to/classes"; CustomClassLoader customLoader = new CustomClassLoader( customClassPath, CustomClassLoaderDemo.class.getClassLoader() ); // Load class using custom classloader Class<?> customClass = customLoader.loadClass("com.example.MyClass"); Object instance = customClass.newInstance(); System.out.println("Class loaded by: " + customClass.getClassLoader()); System.out.println("Parent: " + customClass.getClassLoader().getParent()); } } ClassLoader Methods and Features
public class ClassLoaderMethodsDemo { public static void main(String[] args) { ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); // Important methods System.out.println("Name: " + appClassLoader.getName()); System.out.println("Parent: " + appClassLoader.getParent()); // Get resource java.net.URL resource = appClassLoader.getResource("application.properties"); System.out.println("Resource: " + resource); // Get resources try { java.util.Enumeration<java.net.URL> resources = appClassLoader.getResources("META-INF/MANIFEST.MF"); while (resources.hasMoreElements()) { System.out.println("Found: " + resources.nextElement()); } } catch (IOException e) { e.printStackTrace(); } // Check if class is loaded System.out.println("String class loaded: " + appClassLoader.getDefinedPackage("java.lang") != null); } } Practical Example: Plugin System
// Plugin interface public interface Plugin { void execute(); String getName(); } // Plugin manager using custom classloaders public class PluginManager { private final Map<String, Plugin> plugins = new HashMap<>(); private final String pluginDirectory; public PluginManager(String pluginDirectory) { this.pluginDirectory = pluginDirectory; } public void loadPlugin(String pluginName) throws Exception { // Create isolated classloader for each plugin URL[] urls = { new File(pluginDirectory).toURI().toURL() }; URLClassLoader pluginClassLoader = new URLClassLoader( urls, getClass().getClassLoader() // Parent is application classloader ); // Load plugin class Class<?> pluginClass = pluginClassLoader.loadClass(pluginName); Plugin plugin = (Plugin) pluginClass.newInstance(); plugins.put(plugin.getName(), plugin); System.out.println("Loaded plugin: " + plugin.getName() + " with classloader: " + pluginClass.getClassLoader()); } public void executePlugin(String name) { Plugin plugin = plugins.get(name); if (plugin != null) { plugin.execute(); } } } Key Points to Remember
- Delegation Model: Child classloaders delegate to parents first
- Uniqueness: Classes are identified by (fully qualified name, classloader)
- Visibility: Child classloaders can see parent-loaded classes, but not vice versa
- Isolation: Different classloaders can load different versions of same class
- No Unloading: Classes cannot be unloaded, but classloaders can be garbage collected
Common Issues and Solutions
public class ClassLoaderIssues { // ClassCastException due to different classloaders public static void demonstrateClassCastIssue() throws Exception { URLClassLoader loader1 = new URLClassLoader(new URL[0]); URLClassLoader loader2 = new URLClassLoader(new URL[0]); Class<?> class1 = loader1.loadClass("java.lang.String"); Class<?> class2 = loader2.loadClass("java.lang.String"); // This would cause ClassCastException if not same classloader System.out.println("Same class? " + (class1 == class2)); } // Resource loading strategies public static void loadResource() { // Different ways to load resources ClassLoader cl = ClassLoaderIssues.class.getClassLoader(); // 1. Using ClassLoader (absolute path) InputStream is1 = cl.getResourceAsStream("com/example/config.properties"); // 2. Using Class (relative to class package) InputStream is2 = ClassLoaderIssues.class.getResourceAsStream("config.properties"); // 3. Using system classloader InputStream is3 = ClassLoader.getSystemResourceAsStream("config.properties"); } } This hierarchical classloading system provides security, isolation, and flexibility in Java applications, allowing different parts of an application to have different class loading policies and dependencies.