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.
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.
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.
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.
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.
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());
}
}
|
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.
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();
|
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.
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.
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.
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.
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.
Class loading within the JVM follows as a delegation hierarchy:
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.
The classloader delegation rules are motivated by the need to avoid loading the same class multiple times.
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.
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.
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.
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 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.
Some common errors during the class loading process include:
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.
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.
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.
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.