Deep Dive into Java ClassLoader, Class Loading Process, and the Parent‑Delegation Model
This article explains when and how Java classes are loaded, details the JVM's ClassLoader implementation—including the native defineClass1 method, InstanceKlass creation, SystemDictionary registration, and parallel loading locks—then demonstrates breaking the parent‑delegation model with a custom ClassLoader and discusses practical usage scenarios.
1: Preface
Recently I was asked by a non‑Java friend to write an in‑depth article about the JVM's ClassLoader and the parent‑delegation model. I decided to share my understanding of how classes are loaded and why the delegation mechanism works.
2: How Java Classes Are Loaded
2.1 When a class is loaded
According to "Deep Understanding of the Java Virtual Machine", a class is loaded when the VM encounters new , getstatic , putstatic instructions, during reflection, when initializing a subclass, at VM startup for the main class, or when using JDK 1.7 dynamic language support. In plain terms, a class loads when it is needed at runtime.
2.2 How a class is loaded
Loading a class is as simple as calling ClassLoader.loadClass() . For example:
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Test.class.getClassLoader().loadClass("com.wangxiandeng.test.Dog");
}
}This code triggers the ClassLoader to load com.wangxiandeng.test.Dog . The JDK API hides many details, which we will uncover.
2.3 How the JVM loads a class
The default user‑program ClassLoader is AppClassLoader , whose root is java.lang.ClassLoader . The loadClass() call eventually reaches the native method ClassLoader.defineClass1() :
static native Class
defineClass1(ClassLoader loader, String name, byte[] b, int off, int len, ProtectionDomain pd, String source);The native method maps to the JNI function Java_java_lang_ClassLoader_defineClass1 , which calls JVM_DefineClassWithSource and ultimately jvm_define_class_common in jvm.cpp . This routine converts the class file into a ClassFileStream and resolves it via SystemDictionary::resolve_from_stream() .
2.4 The role of InstanceKlass
InstanceKlass is the JVM’s internal representation of a Java class, containing annotations, constant pool, fields, methods, etc. It is allocated in the Metaspace (the post‑JDK 8 replacement for PermGen).
class InstanceKlass : public Klass {
protected:
Annotations* _annotations;
ConstantPool* _constants;
Array
* _inner_classes;
Array
* _methods;
Array
* _default_methods;
Array
* _fields;
};The loading flow consists of three main steps:
Determine whether parallel loading is allowed and acquire the appropriate lock.
Parse the class file stream and create an InstanceKlass via KlassFactory::create_from_stream() .
Register the resulting Klass in the SystemDictionary (each ClassLoader has its own private Dictionary).
Parallel loading skips the ClassLoader lock but still locks the SystemDictionary to guarantee a single InstanceKlass per class per loader.
3: Revisiting the Parent‑Delegation Model
Each ClassLoader maintains a Dictionary of the InstanceKlass objects it has loaded. The delegation model ensures that a class is loaded by the parent first, preventing duplicate InstanceKlass objects. Breaking this model can lead to multiple InstanceKlass instances for the same class, causing ClassCastException when the same class is loaded by different loaders.
Example of a custom ClassLoader that bypasses delegation:
public class CustomClassloader extends URLClassLoader {
public CustomClassloader(URL[] urls) { super(urls); }
@Override
protected Class
loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("com.wangxiandeng")) {
return findClass(name);
}
return super.loadClass(name, resolve);
}
}Loading Student with this loader and casting it to the system‑loaded Student results in a ClassCastException because the two InstanceKlass objects are unrelated.
The bytecode instruction checkcast performs the runtime verification by comparing the object's Klass* with the target class's Klass* . If they differ and no inheritance relationship exists, the VM throws ClassCastException :
CASE(_checkcast):
if (STACK_OBJECT(-1) != NULL) {
u2 index = Bytes::get_Java_u2(pc+1);
if (METHOD->constants()->tag_at(index).is_unresolved_klass()) {
CALL_VM(InterpreterRuntime::quicken_io_cc(THREAD), handle_exception);
}
Klass* klassOf = (Klass*) METHOD->constants()->resolved_klass_at(index);
Klass* objKlass = STACK_OBJECT(-1)->klass();
if (objKlass != klassOf && !objKlass->is_subtype_of(klassOf)) {
VM_JAVA_ERROR(vmSymbols::java_lang_ClassCastException(),
SharedRuntime::generate_class_cast_message(objKlass, klassOf), note_classCheck_trap);
}
} else {
// handle null case
}To make a custom loader participate in the lookup chain, you can set it as the system class loader ( -Djava.system.class.loader ) or use Thread.setContextClassLoader . The latter is demonstrated below:
public class Test {
public static void main(String[] args) throws Exception {
URL[] url = { Thread.currentThread().getContextClassLoader().getResource("") };
CustomClassloader custom = new CustomClassloader(url);
Thread.currentThread().setContextClassLoader(custom);
Class
clazz = custom.loadClass("com.wangxiandeng.ClassTest");
Object obj = clazz.newInstance();
Method m = clazz.getDeclaredMethod("test");
m.invoke(obj);
}
}
public class ClassTest {
public void test() throws Exception {
Class
c = Thread.currentThread().getContextClassLoader().loadClass("com.wangxiandeng.Student");
Student s = (Student) c.newInstance();
System.out.print(s.getClass().getClassLoader());
}
}By loading ClassTest with the custom loader and invoking its method via reflection, the constant‑pool resolution of Student also goes through the custom loader, avoiding the default‑loader conflict.
4: Summary
Understanding the JVM's class‑loading mechanism—including the native defineClass1 path, InstanceKlass creation, and SystemDictionary registration—is essential to grasp why the parent‑delegation model works and when it can be safely broken for class isolation or plugin architectures.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.