⚙️ JVM Internals & Memory · Advanced

Class loading in Java

Bootstrap, platform and application loaders; parent delegation; loading, linking, initialization.

🧩 The mysteryYou drop your own java/lang/String.class on the classpath, hoping to patch String for the whole app. The JVM politely ignores you. Who stopped you?

A chain of librarians

Classes are loaded lazily, by class loaders. The bootstrap loader (native code) loads core classes like String. The platform loader loads the other JDK modules. The application loader (named app) loads your classpath. Each one has a parent: app → platform → bootstrap.

Ask your parent first

With parent delegation, a loader asked for a class first passes the request up to its parent, all the way to bootstrap. Only if no parent can find it does the loader load it itself. So core classes always come from the JDK.

// app asked for "java.lang.String"
// -> platform? -> bootstrap: found it!
// app never loads its own copy
🔮 Predict it

Who loaded what?

What does this print?

ClassLoader app =
        ClassLoader.getSystemClassLoader();
var boot = Object.class.getClassLoader();
System.out.println(app.getParent().getName());
System.out.println(boot);
  1. platform null
  2. app bootstrap
  3. platform bootstrap
  4. system null
Show the answer

The app loader's parent is named **platform**. Object comes from the bootstrap loader, which lives in the JVM's native code and has no Java object, so it shows up as **null**.

Load, link, initialize

Loading reads the bytes and creates the Class object. Linking has three parts: verification (is the bytecode safe?), preparation (static fields allocated with default values like 0 or null) and resolution (symbolic references become direct ones). Finally, initialization runs static initializers and assignments.

⚠️ The trap

Shadowing a JDK class

Your own java/lang/String.class on the classpath never wins: the app loader delegates to bootstrap, which already has the real String. And class loaders other than bootstrap are forbidden from defining classes in java.* packages anyway.

// classpath: java/lang/String.class (yours)
String s = "hi"; // still the JDK's String
🤔 Think first

Same bytes, same class?

Two different class loaders each load com.shop.Cart from identical bytes. Are they the same type at runtime?

Think about it, then reveal the answer

No. A runtime type is class name + defining loader. Casting one to the other fails with a ClassCastException like *Cart cannot be cast to Cart*.

💼 In the real world

App servers and plugins

Application servers, IDEs and plugin systems give each app or plugin its own class loader, so two apps can use different versions of a library. The flip side is the famous *X cannot be cast to X* error: the same class loaded twice by different loaders, passed across the boundary.

Key takeaways

  1. Bootstrap loader is native and shows up as null in Java
  2. Parent delegation stops apps from replacing core JDK classes
  3. Linking = verification, preparation, resolution
  4. A runtime type is identified by class name + defining loader

💡 Parent delegation is like asking your manager first: only if nobody above you can handle the request do you do it yourself.

🤯 Did you know?

Before Java 9, the platform loader was called the extension class loader and loaded JARs from a jre/lib/ext folder. Java 9's module system replaced it with the platform loader.

Practice questions

What does this print?

ClassLoader app =
        ClassLoader.getSystemClassLoader();
System.out.println(app.getName());
System.out.println(app.getParent().getName());
System.out.println(
        String.class.getClassLoader());
  1. app bootstrap null
  2. system platform bootstrap
  3. app platform bootstrap
  4. app platform null
Check your answer

app platform null. The application loader is named "app" and its parent is the "platform" loader. String comes from the bootstrap loader, which Java represents as null.

You put your own java/lang/String.class on the classpath, hoping to patch String. What happens?

  1. Every class that uses String fails to compile
  2. Loading is delegated to the parent first, so the JDK's String is used
  3. Your String replaces the JDK's for the whole app
  4. Both versions load and the JVM picks one at random
Check your answer

Loading is delegated to the parent first, so the JDK's String is used. The application loader delegates to its parents, and the bootstrap loader already provides java.lang.String. App loaders are also forbidden from defining classes in java.* packages.

Loading a class doesn't run its static code yet. Next: the exact moment a class wakes up.