Class Loading Process & Parent Delegation

Java class loading follows Load → Link (verify, prepare, resolve) → Initialize. Parent delegation means a class loader first delegates to its parent; only if the parent cannot load the class does the child attempt to load it. This ensures core classes (e.g. java.lang.*) are loaded by Bootstrap/System loaders and prevents applications from overriding JDK classes. This article explains the phases and delegation with examples and a reference table.

Overview

  • Phases: Load (read binary) → Verify (format, bytecode) → Prepare (static default values) → Resolve (symbolic refs → direct refs, can be deferred) → Initialize (run <clinit>, static blocks and assignments).
  • Parent delegation: When ClassLoader.loadClass is called, it first delegates to the parent. If the parent loads the class, that result is returned. Otherwise the child calls findClass. Thus java.lang.* is loaded by Bootstrap/Ext/App; application classes are loaded by App or custom loaders.
  • Breaking delegation: For SPI (e.g. JDBC Driver), the interface (DriverManager) is in rt.jar (Bootstrap), but implementations are on the classpath (App). The thread context class loader lets Bootstrap code obtain the App loader to load implementations. This is a deliberate "break" of parent-first delegation.

Example

Example 1: Delegation chain

Plain text
Custom ClassLoader → AppClassLoader → ExtClassLoader → BootstrapClassLoader
  • To load com.example.Foo: Custom delegates to App, App to Ext, Ext to Bootstrap. Bootstrap does not know it, so control returns: Ext → App → Custom. Custom's findClass reads the class file and calls defineClass.

Example 2: Why String cannot be replaced

  • java.lang.String is loaded by Bootstrap. If the application could load its own "fake" String first, type safety would break. Parent delegation ensures "if the parent can load it, the child does not"; core classes stay with the top-level loader and cannot be replaced by application code.

Example 3: Context class loader (SPI)

Java
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
// Internally uses Thread.currentThread().getContextClassLoader()
// to load implementations from META-INF/services
  • SPI interfaces are in rt.jar (Bootstrap); implementations are on the classpath (App). Bootstrap code uses the context class loader to "reverse delegate" to App and load the implementation classes.

Example 4: Custom ClassLoader (findClass only)

Java
public class MyClassLoader extends ClassLoader {
    private final Path basePath;

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytes = loadClassBytes(name);
        return defineClass(name, bytes, 0, bytes.length);
    }
}
  • Override only findClass; leave loadClass as-is to preserve parent delegation.

Example 5: Phase summary

PhaseContent
LoadRead class binary, create Class object
VerifyFormat, bytecode, symbolic references
PrepareStatic fields get default values (0, null)
ResolveSymbolic refs → direct refs (can be deferred)
InitializeRun <clinit> (static blocks, assignments). Triggered by: new, static access, reflection, subclass init

Core Mechanism / Behavior

  • Delegation: In loadClass, call parent.loadClass first, then findClass. Custom loaders typically override only findClass, not loadClass, to keep delegation.
  • Namespace: The same class name loaded by different ClassLoaders yields different classes (equals is false). Tomcat uses separate ClassLoaders per webapp for isolation.
  • Unloading: A class can be unloaded when it has no references and its ClassLoader is GC-eligible. Application ClassLoaders usually live for the process, so classes rarely unload.

Key Rules

  • Override only findClass in custom ClassLoaders; keep the default loadClass delegation to avoid breaking parent delegation.
  • Understand SPI and the context class loader: when the interface is in Bootstrap and the implementation is on the classpath, use the context class loader to load the implementation.
  • For ClassNotFoundException or NoClassDefFoundError, check classpath, dependencies, ClassLoader hierarchy, and module/package visibility.

What's Next

See JVM Memory Model for method area/Metaspace. See Reflection for class loading triggers.