Which class loaders does a modern JVM create at startup, what does each of them load, and how are they linked to one another?
answer
- Three built-ins: application -> platform -> bootstrap
- Bootstrap is native, no parent, represented by null
- Platform = remaining JDK modules
- Application = class path + application modules
- Parent is a reference, not inheritance
basics
~20 sThree built-in loaders: the bootstrap loader (native, no parent) defines the core runtime classes; the platform loader defines the remaining JDK modules; the application (system) loader defines your code from the class path and module path. They form a chain: application -> platform -> bootstrap.
solid answer
~50 sA running JVM starts with three built-in loaders arranged in a chain. **Bootstrap loader**: implemented inside the VM in native code, so it has no Java object representing it and no parent. It defines the core runtime classes, `java.lang`, `java.util` and the rest of `java.base`, straight from the runtime image. **Platform loader**: defines the remaining JDK platform modules that are not part of the core set, for example `java.sql` or `java.xml.crypto`. Its parent is the bootstrap loader. **Application (system) loader**: defines application classes from the class path and from application modules on the module path. Its parent is the platform loader; it is what `ClassLoader.getSystemClassLoader()` returns and what `getClassLoader()` reports for your own classes. The chain is a *parent* relationship, not inheritance: each loader holds a reference to its parent and consults it before defining a class itself. Each loader is also a namespace: a type's identity is the pair of its name and its defining loader, so which loader defined a class matters, not just the name.
code
java · 10 linesSystem.out.println(String.class.getClassLoader());
// null -> bootstrap loader (native, no Java object)
ClassLoader app = MyApp.class.getClassLoader();
System.out.println(app); // jdk.internal.loader.ClassLoaders$AppClassLoader@...
System.out.println(app.getParent());// jdk.internal.loader.ClassLoaders$PlatformClassLoader@...
System.out.println(app.getParent().getParent()); // null -> bootstrap
System.out.println(ClassLoader.getSystemClassLoader() == app); // true
System.out.println(ClassLoader.getPlatformClassLoader() == app.getParent()); // truego deeper
Name the three loaders, what each loads, and the order of the chain. Knowing that bootstrap shows up as null is the detail interviewers check.
Add that parent is a reference rather than inheritance, that the bootstrap loader is native with no Java object, and that each loader is a namespace so identity is name plus defining loader.
Connect the roles to guarantees: an unsubstitutable core, and namespaces that let one process host conflicting library versions with framework loaders extending the tree downward.
Discuss the arrangement as an isolation mechanism, what the platform anchors, where extension points belong in the tree, and the consequences of choosing where a shared type is defined.
## The startup trio When a JVM starts it creates a small, fixed set of loaders. They are built in, they always exist, and every class in the process is ultimately defined by one of them or by a loader someone created underneath them. **The bootstrap loader** is special because it lives inside the virtual machine itself, written in native code as part of the runtime rather than as a Java class. It defines the classes the runtime cannot function without: `java.lang.Object`, `java.lang.String`, `java.lang.Class`, the collections, and generally the contents of the core module `java.base`. Because it is native, there is no `ClassLoader` instance for it, and it has no parent, it is the root of the chain. It reads its classes from the runtime image shipped with the JDK, not from a JAR you can point at. **The platform loader** sits directly beneath it. Its job is the rest of the platform: JDK modules that are part of the specification or the JDK distribution but not the core set, such as database, XML and cryptography modules. Practically, it exists so the platform can be partitioned, some modules are always present and privileged, others are ordinary platform code. Its parent is the bootstrap loader, and it is reachable from Java code via `ClassLoader.getPlatformClassLoader()`. **The application loader**, also called the system loader, is the one your code meets. It defines classes from the **class path** and from application **modules on the module path**. Its parent is the platform loader. `ClassLoader.getSystemClassLoader()` returns it, and calling `getClassLoader()` on one of your own classes returns this object (unless a framework created a loader beneath it). ## What 'parent' actually means This is the most common misconception worth nailing down: the chain is **not** class inheritance. Each loader simply holds a reference to another loader designated as its parent. `ClassLoader` is an ordinary class; the built-in loaders are instances (except bootstrap, which has none). The chain is a runtime data structure, walkable with `getParent()` until you reach `null`, which represents the bootstrap loader. So the practical shape is: ``` application (system) loader | parent platform loader | parent bootstrap loader (represented by null) ``` Application frameworks and containers add loaders *below* the application loader, typically one per plugin, web application or deployment unit, extending the tree downward. ## Why the split into roles matters Separating the loaders is not bookkeeping; it enforces two properties. First, **trust and integrity of the core**. Because the core classes are defined by a loader nobody can substitute, application code cannot supply its own `java.lang.String` and have it used by the runtime. The core is anchored. Second, **namespaces**. A loaded type's runtime identity is the pair *(binary name, defining loader)*, not the name alone. Two loaders can each define a class called `com.acme.Widget`, and the JVM treats those as two unrelated types. This is what makes containers able to host applications with different versions of the same library in one process, and it is why the loader that defined a class is a meaningful property rather than a trivia detail. It also means the loader that *initiates* a request and the loader that ultimately *defines* the class need not be the same: `getClassLoader()` reports the defining one. ## Reading the chain in practice A few observations that come up constantly: - `String.class.getClassLoader()` returns `null`, because the bootstrap loader has no Java representation. `null` in loader APIs conventionally means bootstrap. - Your class returns the application loader; a JDK class such as one from the SQL module returns the platform loader. - Walking `getParent()` from the application loader gives platform, then `null`. - The built-in loaders in current JVMs are internal implementation types, not the general-purpose URL-based loader class some older code assumes. ## Where classes actually come from The bootstrap and platform loaders read from the runtime image built into the JDK. The application loader reads the class path (directories and JARs, in order) and the module path (application modules). Nothing in the chain is inherently tied to files: a loader is defined by its ability to produce bytes for a name, which is exactly why custom loaders can fetch from anywhere. ## The short version to say out loud Three built-in loaders, chained child-to-parent: application, platform, bootstrap. Bootstrap is native, has no parent and no Java object, and owns the core runtime. Platform owns the remaining JDK modules. Application owns your code. Each is a namespace boundary, so a class's identity includes which loader defined it.
- Is the parent relationship between class loaders the same as class inheritance?No. Each loader stores a reference to another loader as its parent, and you follow it with getParent(); it is a runtime link between instances. The built-in loaders are not subclasses of one another in any meaningful sense, and a custom loader picks its parent by passing it to the ClassLoader constructor rather than by extending anything.
- If two class loaders both define a class named com.acme.Widget, does the JVM see one type or two?Two. A loaded type's identity is the pair of its binary name and its defining loader, so the two definitions are unrelated types even though the names match. Passing an instance across the boundary produces a ClassCastException whose message confusingly shows the same class name twice.
saying these in an interview costs you the question
- Describing the loader chain as class inheritance rather than a parent reference
- Claiming the bootstrap loader is a ClassLoader object you can obtain
- Saying the platform loader loads java.lang and the core classes
- Believing a class's identity is just its fully qualified name, independent of the loader
- Assuming custom loaders sit above the application loader rather than below it