This page discusses how Java classes are loaded for execution. The Java class loader is responsible for loading classes, it dynamically locates and loads Java bytecode (.class) and libraries whether its internal or external into JVM’s runtime data area. The class loader subsystem loads classes in three stages: loading, linking, and initialising.

Loading

Bootstrap class loader

Java classes are loaded by instances of java.lang.ClassLoader. However, class loaders are classes themselves, raising the question of how the java.lang.ClassLoader class is loaded.

This is where the bootstrap or primordial class loader comes into play.

The bootstrap class loader is part of the core JVM and is implemented in native code. It does not need to be loaded by another class loader, and operates at a lower level within the JVM. The bootstrap class loader is responsible for loading the core Java classes that make up the JRE, typically found in the rt.jar file located in the $JAVA_HOME/jre/lib directory. This includes fundamental classes like java.lang, String, and java.util.List. Additionally, the bootstrap class loader serves as the parent of all other ClassLoader instances.

Since bootstrap class loader is part of the JVM and is written in native code, different platforms might have different implementation of bootstrap class loader.

Extension Class Loader

The extension class loader is a child of the bootstrap class loader. It loads classes from the Java Extension Library, typically located in the jre/lib/ext directory. The Java Extension Library includes libraries that are not essential parts of the runtime environment but might be critical for certain functionalities, such as cryptography extensions, file system utilities, and more.

Application/System Class Loader

The application (or system) class loader is a child of the extension class loader. It loads classes from the locations specified in the classpath environment variable (using the -classpath or -cp command line option). All external libraries in this path are loaded by the system class loader. This can include user-written libraries, open-source libraries (such as JUnit and Apache Commons), licensed libraries (such as the Spring Framework), and more.

Delegation hierarchy

Class loaders are part of the Java Runtime Environment (JRE). When the JVM requests a class, the class loader tries to locate the class and load its definition into the JVM runtime data area (referred to as “runtime” subsequently) using the fully qualified class name.

The java.lang.ClassLoader.loadClass() method is responsible for loading the class definition into the runtime. It tries to load the class based on the fully qualified name. If the class isn’t already loaded, it delegates the request to the parent class loader. This delegation process happens recursively.

Eventually, if the parent class loader doesn’t find the class, the child class loader calls the java.net.URLClassLoader.findClass() method (or a similar method specific to the class loader implementation) to search for classes in the file system or other specified locations.

If the last class loader in the delegation chain isn’t able to load the class, it throws a java.lang.ClassNotFoundException.

Custom class loader

In scenarios where we need to load classes from locations other than the local hard drive, such as a network, we may need to use custom class loaders. To create a custom class loader, you need to extend the ClassLoader class and override the findClass() method.

public class CustomClassLoader extends ClassLoader {
 
    @Override
    public Class findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromFile(name);
        return defineClass(name, b, 0, b.length);
    }
 
    private byte[] loadClassFromFile(String fileName)  {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
                fileName.replace('.', File.separatorChar) + ".class");
        byte[] buffer;
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        int nextValue = 0;
        try {
            while ( (nextValue = inputStream.read()) != -1 ) {
                byteStream.write(nextValue);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        buffer = byteStream.toByteArray();
        return buffer;
    }
}

Detailed specification and methods in ClassLoader refer to: https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html

Linking

A class or interface must be completely loaded before it is linked. The linking process involves three steps: verification, preparation, and resolution.

  1. Verification: The Java bytecode verifier comes to ensure that the bytecode is well-formed and adheres to JVM specifications. This step checks the correctness of the code to prevent security violations (e.g. unauthorised access to memory) or JVM crashes.
  2. Preparation: Allocates memory for static variables and initialises them with default values. This step sets up the initial memory layout for the class.
  3. Resolution: Resolves symbolic references to other classes, methods, or fields. This step involves converting symbolic names into direct references. Symbolic references could be to other classes (through inheritance or interface implementation), methods, or fields.

If the resolution step fails, a LinkageError is thrown. Examples of such errors include NoClassDefFoundError, NoSuchMethodError, or NoSuchFieldError.

Initialisation

A class or interface must be completely verified and prepared before it is initialised. Initialisation is the final step in preparing a class for execution. It involves executing the class’s static initialisation blocks and initialising static variables. This phase includes the following key steps:

  1. Static block initialisation: A class can have one or many static initialisation blocks denoted by the keyword static followed by a block of code enclosed in curly braces. These blocks are executed in the order they appear in the code during the initialisation phase of the class.
  2. Static variable initialisation: The initialisation phase also initialises static variables declared with the static keyword in the class. These variables are shared among all instances of the class and are initialised once before any instance of the class is created.

If any error occurs during the initialisation phase, subsequent attempts to use the class will result in an ExceptionInInitializerError being thrown.

Main method Invocation

Once the initialisation phase is finished, the JVM looks for the main method (public static void main(String[] args)) in the ‘main’ class. After locating the main method, the JVM starts executing it. The main method serves as the entry point of the program’s execution.


Back to parent page: Java Platform

Web_and_App_DevelopmentProgramming_LanguagesJavaJVMLoadingClassLoaderBootstrap_ClassLoaderExtension_ClassLoaderApplication_ClassLoaderLinkingInitialisation

Reference: