What is a Java ClassLoader?

The Java Classloader, an integral component of the Java Runtime Environment (JRE), operates dynamically by loading Java classes into the Java Virtual Machine (JVM). Unlike programs written in C or C++, a Java program is not comprised of a solitary executable file, but rather consists of numerous individual class files, each representing a distinct Java class. Typically, classes are loaded into memory on an as-needed basis, meaning that these Java class files are not all loaded simultaneously. Instead, they are fetched into memory when required by the program, facilitated by the Class Loader.

The Class Loader serves as a crucial element within the Java Execution Engine, responsible for retrieving binary data from the .class files available in the classpath and loading it into the Method Area. The loading process of a class into the method area transpires solely during the initial encounter of the class within the running Java application. Subsequent references to the class utilize the preexisting data stored in the method area, unless the class has been explicitly unloaded.

ClassLoader in Java works on three principle:

  1. Delegation
  2. Visibility
  3. Uniqueness

The Delegation principle in class loading entails the forwarding of class loading requests to the parent class loader. Only when the parent class loader is unable to locate or load the requested class, does the current class loader proceed with loading the class. This principle establishes a hierarchical structure in which class loading responsibilities are delegated, ensuring a systematic and efficient approach to class loading.

The Visibility principle complements the Delegation principle by granting the child class loader the ability to access all classes loaded by its parent ClassLoader. However, the inverse is not true: the parent class loader does not possess visibility into classes loaded by its child. This principle establishes clear boundaries and encapsulation between class loaders, enabling each class loader to maintain its own set of loaded classes while still allowing access to shared classes in the parent class loader.

The Uniqueness principle is another vital aspect of class loading, ensuring that a class is loaded only once. This principle is primarily accomplished through the implementation of the Delegation principle, which prevents redundant class loading. By delegating the responsibility of class loading to the parent class loader, the child class loader avoids reloading a class that has already been loaded by the parent. This guarantees that each class is loaded exactly once, eliminating unnecessary duplication and maintaining consistency throughout the class loading process.

Every Java Virtual Machine (JVM) is equipped with a built-in class loader known as the primordial class loader. This specific class loader holds a unique status within the virtual machine. It is considered special because the JVM assumes that the primordial class loader has access to a repository of trusted classes that can be executed by the virtual machine without undergoing the usual verification process.

Upon the initiation of a Java Virtual Machine, three distinct class loaders are employed:

  1. Bootstrap Class Loader: The bootstrap class loader, also referred to as the primordial class loader, is responsible for loading essential classes that form the foundation of the Java platform. These classes are typically included in the JVM implementation itself and are considered fundamental for the operation of the virtual machine. The bootstrap class loader is an integral part of the JVM and operates at the lowest level.
  2. Extension Class Loader: The extension class loader is responsible for loading classes from the Java extension directories, such as the "lib/ext" directory in the Java installation. This class loader facilitates the utilization of additional libraries and extensions that are not part of the core Java platform. It operates as an intermediate class loader, positioned between the bootstrap class loader and the application class loader.
  3. Application Class Loader: The application class loader, also known as the system class loader, handles the loading of classes from the application's classpath. It is responsible for locating and loading the application-specific classes and resources. The application class loader forms the topmost layer in the class loading hierarchy and is typically implemented by the JVM to meet the requirements of a specific application or environment.

Building a SimpleClassLoader

A class loader starts by being a subclass of java.lang.ClassLoader . The only abstract method that must be implemented is loadClass(). The flow of loadClass() is as follows:

  1. Verify class name.
  2. Check to see if the class requested has already been loaded.
  3. Check to see if the class is a "system" class.
  4. Attempt to fetch the class from this class loader's repository.
  5. Define the class for the Virtual Machine.
  6. Resolve the class.
  7. Return the class to the caller.

How the very first class loaded?

Class loaders in Java follow a hierarchical structure. The initial class is loaded through the assistance of the public static main() method declared in your class. Once the initial class is loaded and executed, subsequent classes are loaded by the classes that are already loaded and running within the program.

The process starts with the entry point of the Java program, typically defined by the main() method. The initial class containing the main() method is loaded by the JVM's built-in class loader, such as the application class loader. This class loader is responsible for locating and loading the classes specified in the main() method.

As the program execution progresses, additional classes are required to fulfill the program's functionality. These classes are loaded by the class loaders of the previously loaded classes. Each class loader is responsible for locating and loading the classes it depends on.

Classloader hierarchy

When a new Java Virtual Machine (JVM) is launched, the initial responsibility of loading crucial Java classes, including those from the java.lang package, as well as other runtime classes, falls upon the bootstrap classloader. Positioned as the parent of all other classloaders, the bootstrap classloader assumes a unique role without a parent of its own. Following the bootstrap classloader, the extension classloader takes center stage in the loading process.

As a child of the bootstrap classloader, the extension classloader assumes the task of loading classes from all .jar files residing in the java.ext.dirs path. These classes are made available regardless of the Java Virtual Machine's classpath. This classloader plays a key role in the second phase of class loading.

Lastly, the system classpath classloader takes the spotlight, being of utmost importance to developers. Positioned as the immediate child of the extension classloader, it takes charge of loading classes from directories and jar files that are specified through various means. These specifications can include the CLASSPATH environment variable, the java.class.path system property, or the -classpath command line option.

Most Java programmers will never need to explicitly use class loaders (except to load resources so that it still works when they're bundled in JARs), let alone write their own. ClassLoaders are used in very large systems and server applications to do things like:

  1. Modularize a system and load, unload and update modules at runtime
  2. Use different versions of an API library (e.g. an XML parser) in parallel
  3. Isolate different applications running within the same JVM (ensuring they don't interfere with each other, e.g. through static variables)

Class loaders play a vital role within the Java Virtual Machine (JVM) as functional components responsible for loading class data from .class files or even from remote sources, into the Method Area situated in the Heap. Each class loader operates within its own distinct namespace, ensuring that classes invoked by a specific class loader remain isolated within its namespace. Consequently, classes invoked by different class loaders lack visibility over each other, thereby enhancing security measures.

The parent-child delegation mechanism employed by class loaders further reinforces the security aspect. This mechanism guarantees that unauthorized code cannot compromise Java API classes. By delegating the loading process to parent class loaders, the Java runtime system establishes a robust safeguard against potential hacking attempts. The delegation mechanism ensures that only authorized class loaders are granted access to the Java API classes, thus maintaining the integrity and security of the Java environment.

The presence of class loaders provides an abstraction layer that shields the Java runtime from the complexities of file systems and file operations when executing Java programs. Class loaders abstract away the intricacies of locating and loading class files, freeing the Java runtime from the need to directly interact with the underlying file system.

One noteworthy aspect is that Java ClassLoader itself is implemented in the Java language. This feature bestows the advantage of simplicity and flexibility upon developers, as it enables them to create their own custom class loaders without investigating into the intricate details of the JVM. Developers have the freedom to tailor class loading behavior to suit specific requirements, without requiring an in-depth understanding of the internal workings of the JVM.