Class Loaders in Java

Java Classloaders have a very important role inside the JVM. Classloaders are responsible for loading Java classes at runtime. Java Classloaders are part of the Java Runtime Environment.

Classloaders do not load all Java classes into memory at once. Java classes are loaded into memory on demand – when they are required.

What Is A Java Classloader

A classloader in Java is an object, it is an instance of a class that extends java.lang.ClassLoader. The Java runtime allows us to provide our own classloader implementation by extending java.lang.ClassLoader. This practice is useful if we need to load classes by some custom mechanism.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static abstract class ClassLoader {
    public Class loadClass(String name);

    protected final Class<?> defineClass(byte[] b, int off, int len) throws ClassFormatError


    protected Class<?> findClass(String name) throws ClassNotFoundException

    public URL getResource(String name);

    public Enumeration getResources(String name);

    public ClassLoader getParent()
}

Above we can see some of the important methods of java.lang.Classloader. Let’s look at some of these, to get an understanding of what each one of these methods does.

ClassLoader loadClass

The loadClass method loads classes into the JVM. The load class method takes as its argument the fully qualified class name, and it returns an Object instance of class Class.

Classloader defineClass

The defineClass method is a final method and cannot be overridden so a custom classloader cannot perform an action that damages the integrity of the JVM . The main purpose of this method is to validate and materialize a Java class, read from a class file inside the JVM. This method validates the class definition to ensure it adheres to the expected class file format as defined by the Java Virtual Machine Specification, otherwise this method throws a ClassFormatError.

Classloader findClass

The purpose of the findClass method is to find the specified class for the given fully qualified name. Moreover, this method does not load the class.

Classloader getResource

The getResource and getResources methods are useful when we wish to locate resources such as configuration files etc, that are deployed with Java class files.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static File getResourceFile(String fileName) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

    URL resource = classLoader.getResource(fileName);

    if (resource == null) {
        throw new ResourceNotFoundException("File Not Found");
    } else {
        return new File(resource.getFile());
    }
}

Classloader getParent

The Classloader getParent method has a very important role when loading classes inside the JVM. The getParent method is mainly used with the classloader delegation model and helps protect the JVM from executing malicious code.

Classloaders Load Classes Dynamically

As mentioned a Java classloader loads classes when they are required.

1
2
3
4
5
6
public class DoSomething {
    public void do() {
       SomethingElse somethingElse = new SomethingElse();
       somethingElse.do();
   }
}

Above when the do method of the DoSomething class is called, if the SomethingElse class is not loaded into the JVM, the JVM will attempt to load the class via the class loading delegation model. If an appropriate class is found the class will be loaded via a classloader using the methods we looked at previously such as loadClass, and defineClass. For this reason, we can understand that every object in Java has a relationship with its class, and every class has a relationship with its classloader.

1
2
somethingElse.getClass();
somethingElse.getClass().getClassLoader();

Classloader Types

Java provides different types of classloaders. These classloaders have different responsibilities and follow a delegation model, to safely load classes and protect the JVM from malicious code.

Bootstrap Classloader

The bootstrap classloader also known as the Primodial Classloader is used to load Java classes that come packaged with the JVM, such as those from the java.lang package. The bootstrap classloader primarily loads classes from the rt.jar file. The bootstrap classloader is the parent of all classloaders within the JVM.

Extension Classloader

The extension classloader is a child of the bootstrap classloader. The extension classloader loads classes and jar files from the jre/lib/ext directory, or from directories listed via java.ext.dirs system property.

System Classloader

The system classloader is a child of the extension classloader. This is the classloader that loads classes from directories and jar files listed on the Java system classpath. The classpath can be configured using the CLASSPATH environment variable, java.class.path system property or -classpath (-cp) command line option.

Java code that you write as part of your applications will be loaded by this classloader.

How Do Classloaders Work

As mentioned classloaders are part of the Java Runtime Environment. When the time comes to load a new class, the JVM requests the classloader to find and load the class definition into the runtime using the fully qualified class name.

Classloader Delegation Model

Class loading within the JVM follows as a delegation hierarchy:

Java ClassLoading Delegation

Classloaders normally delegate resolving classes and resources by first delegating to their parent classloader, before searching their own classpath. In this case if the parent classloader is unable to locate the resource or class, only then will the classloader attempt to resolve and load the class itself.

Unique Classes

The classloader delegation rules are motivated by the need to avoid loading the same class multiple times.

Classloader Security Guarantees

Moreover, by always delegating to the parent classloader first, the JVM can enforce some security guarantees that can prevent nefarious actions. For example, replacing a system class from the java.lang.package with a malicious piece of code.

Classloader Visibility

Classloaders enforce the principle of visibility. In that classes loaded by a parent Classloader are visible to the child classloaders. However, a class loaded by a child classloader is not visible to the parent classloader.

For example, if class App is loaded by the system (application classloader) and class EXT is loaded by the extension classloader, then both the classes App and EXT are visible to the system classloader. However, class APP is not visible to the extension classloader.

Custom Classloader

We mentioned previously that we can create a custom classloader, to load classes via a custom mechanism. For example, we could load classes from a network location, custom directory, package format etc.

For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class CustomClassLoader extends ClassLoader {
    private final String directory;

    public CustomClassLoader() {
        this(System.getProperty("user.dir"));
    }

    public CustomClassLoader(String directory) {
        this.directory = directory;
    }

    @Override
    public Class findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromDirectory(name);
        return defineClass(name, b, 0, b.length);
    }

    private byte[] loadClassFromDirectory(String fileName) {
        final String classFile = fileName.replace('.', File.separatorChar) + ".class";
        final File classFileToLoad = new File(directory, classFile);

        InputStream is = null;
        byte[] buffer;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        int next = 0;

        try {
            is = new BufferedInputStream(new FileInputStream(classFileToLoad));

            while ((next = is.read()) != -1) {
                bos.write(next);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                    ;
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            }
        }

        buffer = bos.toByteArray();
        return buffer;
    }
}

Above, we define a custom classloader that will attempt to read a class file from a custom directory if given, otherwise the directory pointed to by the user.dir system property.

J2EE Classloader Delegation

In a J2EE application there is a container classloader hierarchy. In this model the container has a classloader, EAR modules and WAR modules also have their own classloaders. This can cause a number of issues and we will look at some of these below.

When working with a Java Web Container it is normally recommended that the containers classloader resolve classes using the local classloader before delegating to its parent. This is because the container may contain jar files and classes that are shared across applications. By resolving from the local classloader first, it gives the possibility that an application can use its own version of classes loaded from the classloader of the application.

A number of J2EE web and application containers, allow you to configure the classloader delegation scheme.

Context Classloaders

Context classloaders provide an alternative delegation scheme. Context classloaders are , useful when we face issues loading classes and resources in a particular environment – when classes and resources cannot be resolved. For example a web framework or plugin can set the classloader to use for loading classes.
The java.lang.Thread class provides a method getContextClassLoader() that returns the ContextClassLoade for that thread.

By switching the classloader we can help the JVM locate the required classes and resources. If the value of the context classloader is not set it defaults to the classloader context of the parent thread.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
ClassLoader originalClassLoader = Thread.currentThread()
        .getContextClassLoader();
Thread.currentThread().setContextClassLoader(
        PlayUtils.classloader());

try {
    ((GenericApplicationContext) applicationContext).refresh();
} catch (BeanCreationException e) {
    Throwable ex = e.getCause();
    if (ex instanceof PlayException) {
        throw (PlayException) ex;
    } else {
        throw e;
    }
} finally {
    Thread.currentThread().setContextClassLoader(
            originalClassLoader);
}           

For example above is some code for integrating the Spring framework in a Play Framework application. In the above code the context classloader is switched to correctly resolve the applications classes.

Common Class loading Errors

Some common errors during the class loading process include:

NoClassDefFoundError

This is one of the most common errors especially when working in a J2EE environment. This exception signifies that the Java Virtual Machine tried to load a class but the class definition could not be found. Moreover, it means that the class definition existed at the time of compilation, but the definition cannot be resolved at runtime.

ClassCastException

This one normally signifies an issue when performing a downcasting operation. However, it can also be the the result of problems with the classloading context. For example, this one is common in a J2EE environment when classes are loaded from different locations, which results in the class existing in different classloaders.

NoSuchMethodError

In relation to class loading this method refers to fact that a class exists but an incorrect version of the class is loaded and the method is not found. This can be the result of different versions of classes or jar files existing on the classpath. Alternatively, the class may exist in a different context as we discussed above.

Conclusion

In this tutorial we discussed classloaders in Java. We introduced the importance of classloaders and how they work. Moreover, we covered the classloader delegation models, and some of the errors that occur when working with classloaders.