Loading is the first stage in java class life cycle.
During this stage, class loader is in charge of loading the class definition into the runtime area using the fully qualified class name.
What You Need
- About 9 minutes
- A favorite text editor or IDE
- Java 8 or later
Different Type of Class Loaders
From the perspective of the jvm, there are only two different class loaders :
- Startup class loader : It is implemented using C++ and is a part of jvm;
- All other class loaders : These class loaders are implemented in Java language, independent of jvm, and all inherit from abstract classes java.lang.ClassLoader, they needs to be loaded into memory by the startup class loader before it can load other classes.
From the perspective of a java developer, class loaders can be divided into below categories :

- Bootstrap ClassLoader : it is responsible for loading the class library (such as rt.jar, all classes starting with java.* are loaded by Bootstrap ClassLoader).
The startup class loader cannot be directly referenced by a Java program, it is always null;
- Extension ClassLoader : it is responsible for loading all class libraries (such as classes starting with javax.*) in the JDK\jre\lib\ext directory, or in the path specified by the java.ext.dirs system variable, Developers can use the extension class loader directly;
- Application ClassLoader : it is responsible for loading the classes specified by the user class path (ClassPath).
Developers can use this class loader directly if the application has not customized its own class loader.
Normally this is the default class loader in the program;
- User Defined Custom ClassLoader : if necessary, we can also add custom class loaders.
Because the ClassLoader that comes with the JVM only knows how to load standard java class files from the local file system, User Defined ClassLoader allows to get java classes from specific places, such as databases and networks.
public class ClassLoadingTest3 {
public static void main(String[] args) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
ClassLoader cl2 = cl.getParent();
ClassLoader cl3 = cl2.getParent();
// system class loader
System.out.println(cl);
// extension class loader (platform class loader)
System.out.println(cl2);
// bootstrap class loader
System.out.println(cl3);
ClassLoader scl = ClassLoader.getSystemClassLoader();
ClassLoader ecl = scl.getParent();
ClassLoader bcl = ecl.getParent();
System.out.println(cl == scl);
System.out.println(cl2 == ecl);
System.out.println(cl3 == bcl);
}
}
Below is the output of above code snippet :
jdk.internal.loader.ClassLoaders$AppClassLoader@4f2410ac
jdk.internal.loader.ClassLoaders$PlatformClassLoader@41b6f21c
null
true
true
true
The method getClassLoader of Class returns the class loader that loaded the class :
public class ClassLoadingTest4 {
public static void main(String[] args) throws ClassNotFoundException {
// boot strap class loader
System.out.println(String.class.getClassLoader());
// application class loader (system class loader)
System.out.println(ClassLoadingTest4.class.getClassLoader());
// array's class loader depends on its type
String[] stringArr = new String[2];
// String is loaded by boot strap class loader
// so string array has boot strap class loader
System.out.println(stringArr.getClass().getClassLoader());
ClassLoadingTest4[] myArr = new ClassLoadingTest4[2];
// Class created by developer is loaded by application class loader
// so here it is application class loader (system class loader)
System.out.println(myArr.getClass().getClassLoader());
// primitive array does not have any class loader
int[] intArr = new int[2];
// here null does not stand for boot strap class loader
// it means no class loader
System.out.println(intArr.getClass().getClassLoader());
}
}
Below is the output of above code snippet :
null
jdk.internal.loader.ClassLoaders$AppClassLoader@4f2410ac
null
jdk.internal.loader.ClassLoaders$AppClassLoader@4f2410ac
null
Load a Class
The method loadClass of ClassLoader can be used to load a class.
Another way to load a class is to use Class.forName().
The difference between the two is that ClassLoader.loadClass() will not trigger the initialization but Class.forName() will :
public class ClassLoadingTest5 {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader cl = ClassLoadingTest5.class.getClassLoader();
cl.loadClass("ClassLoadingTest5$A");
Class.forName("ClassLoadingTest5$B");
// when using a classloader
// the second parameter allows to do the initialization explicitly
// if it is false, then there is no initialization
Class.forName("ClassLoadingTest5$C", false, cl);
// if it is true, then there is initialization
Class.forName("ClassLoadingTest5$D", true, cl);
}
private static class A {
static {
System.out.println("A is initialized");
}
}
private static class B {
static {
System.out.println("B is initialized");
}
}
private static class C {
static {
System.out.println("C is initialized");
}
}
private static class D {
static {
System.out.println("D is initialized");
}
}
}
Below is the output of above code snippet :
B is initialized
D is initialized
A java class can be loaded by class loader actively and passively :
public class ClassLoadingTest {
/**
* -XX:+TraceClassLoading
* -Xlog:class+load=info for JDK 17+
*/
public static void main(String[] args) throws ClassNotFoundException {
// actif
Class<?> clazz = Class.forName("ClassLoadingTest$A");
// actif
ClassLoader classLoader = ClassLoadingTest.class.getClassLoader();
Class<?> loadClass = classLoader.loadClass("ClassLoadingTest$A");
// passif
A a = new A();
}
private static class A {
}
}
When running above code snippet with -XX:+TraceCLassLoading or -Xlog:class+load=info for JDK 17+, in the output, we can remark below :
[0,078s][info][class,load] ClassLoadingTest$A source: file:/home/ovo/.config/Code/User/workspaceStorage/39f309f7697bdc3c26769b77ce745e7e/redhat.java/jdt_ws/BlogTests_e0c01ebd/bin/
Comparing two loaded classes for equality makes sense only if they are loaded by the same class loader :
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
public class ClassLoadingTest2 {
public static void main(String[] args) throws ClassNotFoundException, IOException {
URL[] urls = new URL[1];
urls[0] = new URL(
"file:/home/ovo/.config/Code/User/workspaceStorage/59afdb4fac8e73818aa12a84de15ed2b/redhat.java/jdt_ws/BlogTests_e0c01ebd/bin/");
ClassLoader parentClassLoader = null;// ClassLoadingTest2.class.getClassLoader();
MyClassLoader cl1 = new MyClassLoader("cl1", urls, parentClassLoader);
String class2load = "ClassLoadingTest2$A";
Class<?> clazz1 = cl1.loadClass(class2load);
Class<?> clazz2 = cl1.loadClass(class2load);
// clazz1 and clazz2 are both loaded by the same class loader cl1
// so they are the same
System.out.println(clazz1 == clazz2 && clazz1.equals(clazz2));
MyClassLoader cl2 = new MyClassLoader("cl2", urls, parentClassLoader);
clazz2 = cl2.loadClass(class2load);
// clazz1 is loaded by class loader cl1
// clazz2 is loaded by class loader cl2
// since the class loader is not the same
// even if the loaded class name is the same
// but the final loaded class are not the same
System.out.println(clazz1 == clazz2 && clazz1.equals(clazz2));
cl1.close();
cl2.close();
}
private static class MyClassLoader extends URLClassLoader {
public MyClassLoader(String name, URL[] urls, ClassLoader parent) {
super(name, urls, parent);
}
}
private static class A {
}
}
Below is the output of above code snippet :
true
false
Parents Delegate Mechanism
Class loader uses parents delegate mechanism to load a class :

- When AppClassLoader loads a class, it does not try to load the class itself first, but delegates the class loading request to the parent class loader ExtClassLoader to complete;
- When ExtClassLoader loads a class, it doesn’t try to load the class itself first, but delegates the class loading request to BootStrapClassLoader to complete;
- If BootStrapClassLoader fails to load (for instance, the class is not found in $JAVA_HOME/jre/lib), ExtClassLoader will be used to load;
- If ExtClassLoader also fails to load, it will use AppClassLoader to load, if AppClassLoader also fails to load, an exception ClassNotFoundException will be reported.
The advantage of parent delegate mechanism consists of :
- avoiding duplicate loading of classes and guaranteeing the global uniqueness of the class;
- protecting program security and preventing core api from being maliciously modified.
The implement of parent delegate mechanism is located in loadClass method of abstract class ClassLoader :
/**
* Loads the class with the specified <a href="#binary-name">binary name</a>. The
* default implementation of this method searches for classes in the
* following order:
*
* <ol>
*
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li>
*
* <li><p> Invoke the {@link #loadClass(String) loadClass} method
* on the parent class loader. If the parent is {@code null} the class
* loader built into the virtual machine is used, instead. </p></li>
*
* <li><p> Invoke the {@link #findClass(String)} method to find the
* class. </p></li>
*
* </ol>
*
* <p> If the class was found using the above steps, and the
* {@code resolve} flag is true, this method will then invoke the {@link
* #resolveClass(Class)} method on the resulting {@code Class} object.
*
* <p> Subclasses of {@code ClassLoader} are encouraged to override {@link
* #findClass(String)}, rather than this method. </p>
*
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock getClassLoadingLock} method
* during the entire class loading process.
*
* @param name
* The <a href="#binary-name">binary name</a> of the class
*
* @param resolve
* If {@code true} then resolve the class
*
* @return The resulting {@code Class} object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
When creating a custom class loader, developers must pay attention not to overwrite the loadClass method directly to avoid breaking the parent delegate mechanism.
Instead, we can overwirte the findClass method while creating a custom class loader.
Below is an example of custom class loader to load class from a user defined folder :
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class ClassLoadingTest6 {
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader cl = new MyClassLoader("/home/ovo/github/BlogTests/test");
Class<?> clazz = cl.loadClass("Person");
System.out.println(clazz.getClassLoader().getClass().getSimpleName());
}
private static class MyClassLoader extends ClassLoader {
private String classFolder;
public MyClassLoader(String classFolder) {
this.classFolder = classFolder;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String separator = File.separator;
String url;
if (!this.classFolder.endsWith(separator)) {
url = this.classFolder + separator + name + ".class";
} else {
url = this.classFolder + name + ".class";
}
Path path = Paths.get(url);
byte[] bytes;
try {
bytes = Files.readAllBytes(path);
Class<?> clazz = defineClass(null, bytes, 0, bytes.length);
return clazz;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
}
Below is the output of above code snippet :
MyClassLoader
It has to pay attention in above example that Person.class should not be under class path of ClassLoadingTest6.
Otherwise, according to the parent delegate mechanism, Person class will be loaded by application class loader and the output will become below :
AppClassLoader