- What is the Java Class Loader?
- Class Loaders
- Class Loading Steps
- Class Loading Mechanism
- Writing You Own Class Loader
The Java Class Loader is a fundamental component of the JVM, so it is important to have a basic understanding of how it works and how you can create your own ClassLoader
.
What is the Java Class Loader?
The Java compiler creates binary files with the .class
extension for each source file. Each class file contains the definition and implementation code and are loaded into memory on demand. The Java Class Loader is responsible for loading the class files into memory.
Class Loaders
Class loaders provided by the Java run-time:
- Bootstrap Class Loader: is built-in to the Java Virtual Machine and represented by
null
in the ClassLoader API. Classes previously stored injre/lib/rt.jar
are now stored in a more efficient format in thelib
directory. - Platform Class Loader: starting from Java 9 the extension class loader has been renamed to platform class loader. All classes in the Java SE Platform are guaranteed to be visible through the platform class loader and can be used as the parent of a
ClassLoader
instance. - System Class Loader (aka application class loader): is usually the class loader used to start the application and used to define classes on the class path or module path, and loads a few JDK modules like
jdk.compiler
,jdk.javadoc
orjdk.jshell
.
In JDK 8 and earlier Bootstrap Class Loader loads classes in
jre/lib/rt.jar
and the extension mechanism allows runtime environment to find and load extension classes without specifically naming them on the class path and loads the code in the extensions directories (<JAVA_HOME>/jre/lib/ext
, or any other directory specified by thejava.ext.dirs
system property).
The following code will print out the loaders of each module in the JVM.
ModuleLayer layer = ModuleLayer.boot();
layer.modules().forEach(module -> {
ClassLoader classLoader = module.getClassLoader();
String classLoaderName = isNull(classLoader) ? "bootstrap" : classLoader.getName();
System.out.println(classLoaderName + ": " + module.getName());
});
Class Loading Steps
- Loading: finds and imports the binary data for a type by its name and creating a class or interface from that binary representation.
- Linking: performs verification, preparation and, optionally, resolution.
- Verification: checks the correctness of the imported type.
- Preparation: allocates memory for class variables and initialize the memory to default values.
- Resolution: transforms symbolic references from the type into direct references.
- Initialization: execute the code that initializes class variables to their starting values.
Class Loading Mechanism
The Java class loading mechanism is based on class-loading delegation. Class loaders have a parent/child relationship and every class except for the bootstrap one has a parent class loader. Class loaders are created with one parent to whom they can delegate the loading. Steps:
- Check if the class has been loaded previously by looking up in cache. If not, it will ask the parent to load the class.
- The previous step repeats recursively.
- If the parent returns
null
or throws aClassNotFoundException
, then the class loader searches for the class on the class path.
A class loader only see classes loaded by itself or its parent; it cannot see classes loaded by its children.
Writing You Own Class Loader
You can simply create your own class loader by extending the ClassLoader
class and override the method findClass
.
When you create you own ClassLoader
you have to:
- Load the class as byte-code from your class path or any other place.
- Call
defineClass
from theClassLoder
superclass convert of an array of bytes into an instance of a class.
public class PathFileClassLoader extends ClassLoader {
public static final String NAME = "com.sergiomartinrubio.javaclassloader.TestClassFromPath";
@Override
protected Class<?> findClass(String filePath) {
// 1. Load bytes from file
byte[] bytes = loadClassBytesFromFile(filePath);
// 2. Create class from bytecode
return defineClass(NAME, bytes, 0, bytes.length);
}
private byte[] loadClassBytesFromFile(String filePath) {
File file = new File(filePath);
try {
return Files.readAllBytes(file.toPath());
} catch (IOException e) {
e.printStackTrace();
}
return new byte[]{};
}
}
then you can create your new class loader, create an instance of a class file and execute methods
PathFileClassLoader pathFileClassLoader = new PathFileClassLoader();
// Load class from the root of the project
Class<?> classFromPath = pathFileClassLoader.findClass("TestClassFromPath.class");
Object classObject = classFromPath.getDeclaredConstructor().newInstance();
Method myMethod = classFromPath.getMethod("hello");
myMethod.invoke(classObject);