Java Class Loaders

James Tapsell
Jrtapsell
Published in
3 min readMay 4, 2017

With Great Power Comes Great Responsibility

Classloaders allow the process that is used to find a class with a given name to be modified by the developer, allowing classes to be defined at runtime, and even allowing classes to be loaded from sources not supported by the JVM out of the box.

The Java SE 8 JavaDoc defines a Classloader as:

… an object that is responsible for loading classes. ... Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class.
<https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html>

There are limitations on ClassLoaders:

  1. A ClassLoader must return the same Class object on multiple calls to loadClass
  2. Types loaded by different ClassLoaders are different, and cannot be cast between.
  3. If a ClassLoader Cl1 delegates class C to another ClassLoader Cl2, then for any super class or interface of C, or class referred to by class C, Cl1 and Cl2 must return the same Class object

Breaking the first of these limitations leads to the following error message.

Exception in thread “main” java.lang.LinkageError: loader (instance of MyClassLoader): attempted duplicate class definition for name: “Test”

Breaking the second of these causes the following error message

Exception in thread “main” java.lang.ClassCastException: Test cannot be cast to Test

This is because although in error messages classes are referred to by their binary name, within the JVM they are treated as separate, as they have different loaders

Breaking the third limitation results can cause classes to act in different ways depending on which ClassLoader loaded them, which makes debugging intermittent issues a lot more difficult.

There are techniques to get around these limitations:

To get around the first limitation, multiple ClassLoaders can be used, so that each one only defines the loaded class once, but this has to be used with caution, as a ClassLoader can only be garbage collected once all the instances of classes defined by it have been garbage collected.

The second limitation can be solved by having an Interface or base class loaded by the system, and only referring to loaded instances as instances of this type, so there is no need to cast between ClassLoaders, as the base was not loaded by a dynamic loader.

Diagram of how a base interface can be used to allow static code to work with dynamic classes

The third is solved by the way that ClassLoaders are meant to delegate. They are meant to delegate up to the root ClassLoader, and then back to the first ClassLoader that can load the requested class, this means that as long as a ClassLoader can load a class, it can load all the dependencies, and as it is checked before the classloader is delegated from it will return the same result as if it had been called directly.

For dynamic class loading, I had an class that was used to make instances of a class from disk, but rather than directly being a ClassLoader it wrapped instances of a custom ClassLoader, which meant that I could bypass the single definition rule, as each instance of my ClassLoader only loaded a class once.

There was also a base interface, which exposed all the required methods, so the static code could call methods on the dynamic classes.

--

--