Java Virtual Machine Internals, Part 1: Class Loader
The Java Virtual Machine is the heart of the Java ecosystem. Thanks to the JVM, when it comes to Java programs, we can 'write once, run everywhere.' Like other virtual machines, the JVM is also an abstract computer. The Java Virtual Machine's main job is to load class files and execute the bytecode they contain.
There are multiple components of the Java Virtual Machine like class loader, the garbage collector (automatic memory management), interpreter, JIT compiler, thread management. In this series, I’ll be discussing how the Java Virtual Machine works. In this first installment, we are going to talk about the class loader. So let's get started!
The class loader loads class files from both the program and the Java API. Only those class files from the Java API that are actually needed by a running program are loaded into the virtual machine.
The bytecodes are executed in an execution engine.
What Is Class Loading?
Class loading is finding and loading types (classes and interfaces) at runtime dynamically. Types data are contained in binary files in class file format.
Phases of Class Loading
The class loader subsystem is responsible for more than just locating and importing the binary data for classes. It must also verify the correctness of imported classes, allocate and initialize memory for class variables, and assist in the resolution of symbolic references. These activities are performed in a strict order:
- Loading: finding and importing the binary data for a type with a particular name and creating a class or interface from that binary representation.
- Linking: performing verification, preparation, and (optionally) resolution
- Verification: ensuring the correctness of the imported type
- Preparation: allocating memory for class variables and initializing the memory to default values
- Resolution: transforming symbolic references from the type into direct references.
- Initialization: invoking Java code that initializes class variables to their proper starting values.
Note: In addition to loading classes, a class loader is also responsible for locating resources. A resource is some data (a ".class" file, configuration data, or an image for example) that is identified with an abstract '/'-separated path name. Resources are typically packaged with an application or library so that they can be located by code in the application or library.
The Java Class Loading Mechanism
The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a "parent" class loader. When loading a class, a class loader first "delegates" the search for the class to its parent class loader before attempting to find the class itself.
The class loader delegation model is the graph of class loaders that pass loading requests to each other. The bootstrap class loader is at the root of this graph. Class loaders are created with a single delegation parent and look for a class in the following places:
- Cache
- Parent
- Self
A class loader first determines if it has been asked to load this same class in the past. If so, it returns the same class it returned last time (that is, the class stored in the cache). If not, it gives its parent a chance to load the class. These two steps repeat recursively and depth first. If the parent returns null (or throws a ClassNotFoundException
), then the class loader searches its own path for the source of the class.
Because the parent class loader is always given the opportunity to load a class first, the class is loaded by the class loader nearest the root. This also has the effect of only allowing a class loader to see classes loaded by itself or its parent or ancestors; it cannot see classes loaded by its children.
The Java SE Platform API historically specified two class loaders:
Bootstrap class loader: which loads classes from the bootstrap class path.
System class loader: which is the default delegation parent for new class loaders and, typically, the class loader used to load and start the application.
Run-Time Built-in Class Loaders (JDK 9+)
Application class loader: The application class loader is typically used to define classes on the application class path. It's default loader for JDK modules that provide tools or export tool APIs.
Platform class loader: It is selected (based on security/permissions) by Java SE and JDK modules. For e.g. java.sql
Bootstrap class loader: It defines the core Java SE and JDK modules.
The three built-in class loaders work together to load classes as follows:
- The application class loader first searches the named modules defined to all of the built-in loaders. If a suitable module is defined as one of these loaders, then that loader will load the class. If a class is not found in a named module defined to one of these loaders, then the application class loader delegates to its parent. If a class is not found by its parent, then the application class loader searches the class path. Classes found on the class path are loaded as members of this loader's unnamed module.
- The platform class loader searches the named modules defined to all of the built-in loaders. If a suitable module is defined as one of these loaders, then that loader will load the class. If a class is not found in a named module defined to one of these loaders, then the platform class loader delegates to its parent.
- The bootstrap class loader searches the named modules defined to itself. If a class is not found in a named module defined to the bootstrap loader, then the bootstrap class loader searches the files and directories added to the bootstrap class path via the -Xbootclasspath/a option (allows files and directories to be appended to the default bootstrap class path now). Classes found on the bootstrap class path are loaded as members of this loader's unnamed module.
To see the built-in class loaders, you can run the below code:
public class BuiltInClassLoadersDemo {
public static void main(String[] args) {
BuiltInClassLoadersDemo demoObject = new BuiltInClassLoadersDemo();
ClassLoader applicationClassLoader = demoObject.getClass().getClassLoader();
printClassLoaderDetails(applicationClassLoader);
// java.sql classes are loaded by platform classloader
java.sql.Date now = new Date(System.currentTimeMillis());
ClassLoader platformClassLoder = now.getClass().getClassLoader();
printClassLoaderDetails(platformClassLoder);
// java.lang classes are loaded by bootstrap classloader
ClassLoader bootstrapClassLoder = args.getClass().getClassLoader();
printClassLoaderDetails(bootstrapClassLoder);
}
private static void printClassLoaderDetails(ClassLoader classLoader){
// bootstrap classloader is represented by null in JVM
if(classLoader != null) {
System.out.println("ClassLoader name : " + classLoader.getName());
System.out.println("ClassLoader class : " + classLoader.getClass().getName());
}else {
System.out.println("Bootstrap classloader");
}
}
}
With Amazon Corretto 11.0.3 installed on my machine, the above code produces the following output:
ClassLoader name : app
ClassLoader class : jdk.internal.loader.ClassLoaders$AppClassLoader
ClassLoader name : platform
ClassLoader class : jdk.internal.loader.ClassLoaders$PlatformClassLoader
Bootstrap classloader
You can check the ClassLoader APIs here (JDK 11).
Note: HotSpot VM (via Amazon Corretto 11 ) is used as reference JVM implementation in this series.
Stay tuned for part 2 where we take a closer look at JVM internals and the class file format.