Web Front-end12 minute read

Advanced Java Class Tutorial: A Guide to Class Reloading

In Java development, a typical workflow involves restarting the server with every class change, and no one complains about it. But is Java class reloading that difficult to achieve? And could that problem be both challenging and exciting to solve? In this article, I will try to address the problem, help you gain all the benefits of on-the-fly class reloading, and boost your productivity immensely.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

In Java development, a typical workflow involves restarting the server with every class change, and no one complains about it. But is Java class reloading that difficult to achieve? And could that problem be both challenging and exciting to solve? In this article, I will try to address the problem, help you gain all the benefits of on-the-fly class reloading, and boost your productivity immensely.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Lê Anh Quân
Verified Expert in Engineering

Lê has 14 years of experience building web apps, using Java technologies. Over the past 5 years, he has with React and Angular.

Expertise

PREVIOUSLY AT

FPT Software
Share

In Java development projects, a typical workflow involves restarting the server with every class change, and no one complains about it. That is a fact about Java development. We have worked like that since our first day with Java. But is Java class reloading that difficult to achieve? And could that problem be both challenging and exciting to solve for skilled Java developers? In this Java class tutorial, I will try to address the problem, help you gain all the benefits of on-the-fly class reloading, and boost your productivity immensely.

Java class reloading is not often discussed, and there is very little documentation exploring this process. I’m here to change that. This Java classes tutorial will provide a step by step explanation of this process and help you master this incredible technique. Keep in mind that implementing Java class reloading requires a great deal of care, but learning how to do it will put you in the big leagues, both as a Java developer, and as a software architect. It also will not hurt to understand how to avoid the 10 most common Java mistakes.

Work-Space Setup

All source code for this tutorial is uploaded on GitHub here.

To run the code while you follow this tutorial, you will need Maven, Git and either Eclipse or IntelliJ IDEA.

If you are using Eclipse:

  • Run the command mvn eclipse:eclipse to generate Eclipse’s project files.
  • Load the generated project.
  • Set output path to target/classes.

If you are using IntelliJ:

  • Import the project’s pom file.
  • IntelliJ will not auto-compile when you are running any example, so you have to either:
  • Run the examples inside IntelliJ, then every time you want to compile, you’ll have to press Alt+B E
  • Run the examples outside IntelliJ with the run_example*.bat. Set IntelliJ’s compiler auto-compile to true. Then, every time you change any java file, IntelliJ will auto-compile it.

Example 1: Reloading a Class with Java Class Loader

The first example will give you a general understanding of the Java class loader. Here is the source code.

Given the following User class definition:

public static class User {
  public static int age = 10;
}

We can do the following:

public static void main(String[] args) {
  Class<?> userClass1 = User.class;
  Class<?> userClass2 = new DynamicClassLoader("target/classes")
      .load("qj.blog.classreloading.example1.StaticInt$User");
  
  ...

In this tutorial example, there will be two User classes loaded into the memory. userClass1 will be loaded by the JVM’s default class loader, and userClass2 using the DynamicClassLoader, a custom class loader whose source code is also provided in the GitHub project, and which I will describe in detail below.

Here is the rest of the main method:

  out.println("Seems to be the same class:");
  out.println(userClass1.getName());
  out.println(userClass2.getName());
  out.println();

  out.println("But why there are 2 different class loaders:");
  out.println(userClass1.getClassLoader());
  out.println(userClass2.getClassLoader());
  out.println();

  User.age = 11;
  out.println("And different age values:");
  out.println((int) ReflectUtil.getStaticFieldValue("age", userClass1));
  out.println((int) ReflectUtil.getStaticFieldValue("age", userClass2));
}

And the output:

Seems to be the same class:
qj.blog.classreloading.example1.StaticInt$User
qj.blog.classreloading.example1.StaticInt$User

But why there are 2 different class loaders:
qj.util.lang.DynamicClassLoader@3941a79c
sun.misc.Launcher$AppClassLoader@1f32e575

And different age values:
11
10

As you can see here, although the User classes have the same name, they are actually two different classes, and they can be managed, and manipulated, independently. The age value, although declared as static, exists in two versions, attaching separately to each class, and can be changed independently as well.

In a normal Java program, ClassLoader is the portal bringing classes into the JVM. When one class requires another class to be loaded, it’s the ClassLoader’s task to do the loading.

However, in this Java class example, the custom ClassLoader named DynamicClassLoader is used to load the second version of User class. If instead of DynamicClassLoader, we were to use the default class loader again ( with the command StaticInt.class.getClassLoader() ) then the same User class will be used, as all loaded classes are cached.

Examining the way the default Java ClassLoader works versus DynamicClassLoader is key to benefiting from this Java classes tutorial.

The DynamicClassLoader

There can be multiple classloaders in a normal Java program. The one that loads your main class, ClassLoader, is the default one, and from your code, you can create and use as many classloaders as you like. This, then, is the key to class reloading in Java. The DynamicClassLoader is possibly the most important part of this entire tutorial, so we must understand how dynamic class loading works before we can accomplish our goal.

Unlike the default behavior of ClassLoader, our DynamicClassLoader inherits a more aggressive strategy. A normal classloader would give its parent ClassLoader the priority and only load classes that its parent can not load. That is suitable for normal circumstances, but not in our case. Instead, the DynamicClassLoader will try to look through all its class paths and resolve the target class before it gives up the right to its parent.

In our example above, the DynamicClassLoader is created with only one class path: "target/classes" (in our current directory), so it’s capable of loading all the classes that reside in that location. For all the classes not in there, it will have to refer to the parent classloader. For example, we need to load the String class in our StaticInt class, and our class loader does not have access to the rt.jar in our JRE folder, so the String class of the parent class loader will be used.

The following code is from AggressiveClassLoader, the parent class of DynamicClassLoader, and shows where this behavior is defined.

byte[] newClassData = loadNewClass(name);
if (newClassData != null) {
  loadedClasses.add(name);
  return loadClass(newClassData, name);
} else {
  unavaiClasses.add(name);
  return parent.loadClass(name);
}

Take note of the following properties of DynamicClassLoader:

  • The loaded classes have the same performance and other attributes as other classes loaded by the default class loader.
  • The DynamicClassLoader can be garbage-collected together with all of its loaded classes and objects.

With the ability to load and use two versions of the same class, we are now thinking of dumping the old version and loading the new one to replace it. In the next example, we will do just that…continuously.

Example 2: Reloading a Class Continuously

This next Java example will show you that the JRE can load and reload classes forever, with old classes dumped and garbage collected, and brand new class loaded from the hard drive and put to use. Here is the source code.

Here is the main loop:

public static void main(String[] args) {
  for (;;) {
    Class<?> userClass = new DynamicClassLoader("target/classes")
      .load("qj.blog.classreloading.example2.ReloadingContinuously$User");
    ReflectUtil.invokeStatic("hobby", userClass);
    ThreadUtil.sleep(2000);
  }
}

Every two seconds, the old User class will be dumped, a new one will be loaded and its method hobby invoked.

Here is the User class definition:

@SuppressWarnings("UnusedDeclaration")
public static class User {
  public static void hobby() {
    playFootball(); // will comment during runtime
    //  playBasketball(); // will uncomment during runtime
  }
  
  // will comment during runtime
  public static void playFootball() {
    System.out.println("Play Football");
  }
  
  //  will uncomment during runtime
  //  public static void playBasketball() {
  //    System.out.println("Play Basketball");
  //  }
}

When running this application, you should try to comment and uncomment the code indicated code in the User class. You will see that the newest definition will always be used.

Here is some example output:

...
Play Football
Play Football
Play Football
Play Basketball
Play Basketball
Play Basketball

Every time a new instance of DynamicClassLoader is created, it will load the User class from the target/classes folder, where we have set Eclipse or IntelliJ to output the latest class file. All old DynamicClassLoaders and old User classes will be unlinked and subjected to the garbage collector.

It is critical that advanced Java developers understand dynamic class reloading, whether active or unlinked.

If you are familiar with JVM HotSpot, then it’s noteworthy here that the class structure can also be changed and reloaded: the playFootball method is to be removed and the playBasketball method added. This is different to HotSpot, which allows only method content be changed, or the class can not be reloaded.

Now that we are capable of reloading a class, it is time to try reloading many classes at once. Let’s try it out in the next example.

Example 3: Reloading Multiple Classes

The output of this example will be the same with Example 2, but will show how to implement this behavior in a more application-like structure with context, service and model objects. This example’s source code is rather large, so I have only shown parts of it here. The full source code is here.

Here is is the main method:

public static void main(String[] args) {
  for (;;) {
    Object context = createContext();
    invokeHobbyService(context);
    ThreadUtil.sleep(2000);
  }
}

And the method createContext:

private static Object createContext() {
  Class<?> contextClass = new DynamicClassLoader("target/classes")
    .load("qj.blog.classreloading.example3.ContextReloading$Context");
  Object context = newInstance(contextClass);
  invoke("init", context);
  return context;
}

The method invokeHobbyService:

private static void invokeHobbyService(Object context) {
  Object hobbyService = getFieldValue("hobbyService", context);
  invoke("hobby", hobbyService);
}

And here is the Context class:

public static class Context {
  public HobbyService hobbyService = new HobbyService();
  
  public void init() {
    // Init your services here
    hobbyService.user = new User();
  }
}

And the HobbyService class:

public static class HobbyService {
  public User user;
  
  public void hobby() {
    user.hobby();
  }
}

The Context class in this example is much more complicated than the User class in the previous examples: it has links to other classes, and it has the init method to be called every it is instantiated. Basically, it’s very similar to real world application’s context classes (which keeps track of the application’s modules and does dependency injection). So being able to reload this Context class together with all it’s linked classes is a great step toward applying this technique to real life.

Java class reloading is difficult for even advanced Java engineers.

As the number of classes and objects grows, our step of “drop old versions” will also become more complicated. This is also the biggest reason why class reloading is so difficult. To possibly drop old versions we will have to make sure that, once the new context is created, all the references to the old classes and objects are dropped. How do we deal with this elegantly?

The main method here will have a hold of the context object, and that is the only link to all the things that need to be dropped. If we break that link, the context object and the context class, and the service object … will all be subjected to the garbage collector.

A little explanation about why normally classes are so persistent, and do not get garbage collected:

  • Normally, we load all our classes into the default Java classloader.
  • The class-classloader relationship is a two-way relationship, with the class loader also caching all the classes it has loaded.
  • So as long as the classloader is still connected to any live thread, everything (all loaded classes) will be immune to the garbage collector.
  • That said, unless we can separate the code we want to reload from the code already loaded by the default class loader, our new code changes will never be applied during runtime.

With this example, we see that reloading all application’s classes is actually rather easy. The goal is merely to keep a thin, droppable connection from the live thread to the dynamic class loader in use. But what if we want some objects (and their classes) to not be reloaded, and be reused between reloading cycles? Let’s look at the next example.

Example 4: Separating Persisted and Reloaded Class Spaces

Here’s the source code..

The main method:

public static void main(String[] args) {
  ConnectionPool pool = new ConnectionPool();

  for (;;) {
    Object context = createContext(pool);

    invokeService(context);

    ThreadUtil.sleep(2000);
  }
}

So you can see that the trick here is loading the ConnectionPool class and instantiating it outside the reloading cycle, keeping it in the persisted space, and pass the reference to the Context objects

The createContext method is also a little bit different:

private static Object createContext(ConnectionPool pool) {
  ExceptingClassLoader classLoader = new ExceptingClassLoader(
      (className) -> className.contains(".crossing."),
      "target/classes");
  Class<?> contextClass = classLoader.load("qj.blog.classreloading.example4.reloadable.Context");
  Object context = newInstance(contextClass);
  
  setFieldValue(pool, "pool", context);
  invoke("init", context);

  return context;
}

From now on, we will call the objects and classes that are reloaded with every cycle the “reloadable space” and others - the objects and classes not recycled and not renewed during the reloading cycles - the “persisted space”. We will have to be very clear about which objects or classes stay in which space, thus drawing a separation line between these two spaces.

Unless handled properly, this separation of Java class loading can lead to failure.

As seen from the picture, not only are the Context object and the UserService object referring to the ConnectionPool object, but the Context and UserService classes are also referring to the ConnectionPool class. This is a very dangerous situation which often leads to confusion and failure. The ConnectionPool class must not be loaded by our DynamicClassLoader, there must be only one ConnectionPool class in the memory, which is the one loaded by the default ClassLoader. This is one example of why it is so important to be careful when designing a class-reloading architecture in Java.

What if our DynamicClassLoader accidentally loads the ConnectionPool class? Then the ConnectionPool object from the persisted space can not be passed to the Context object, because the Context object is expecting an object of a different class, which is also named ConnectionPool, but is actually a different class!

So how do we prevent our DynamicClassLoader from loading the ConnectionPool class? Instead of using DynamicClassLoader, this example uses a subclass of it named: ExceptingClassLoader, which will pass the loading to super classloader based on a condition function:

(className) -> className.contains("$Connection")

If we don’t use ExceptingClassLoader here, then the DynamicClassLoader would load the ConnectionPool class because that class resides in the “target/classes” folder. Another way to prevent the ConnectionPool class being picked up by our DynamicClassLoader is to compile the ConnectionPool class to a different folder, maybe in a different module, and it will be compiled separately.

Rules for Choosing Space

Now, the Java class loading job gets really confusing. How do we determine which classes should be in the persisted space, and which classes in the reloadable space? Here are the rules:

  1. A class in the reloadable space may reference a class in the persisted space, but a class in the persisted space may never reference a class in the reloadable space. In the previous example, the reloadable Context class references the persisted ConnectionPool class, but ConnectionPool has no reference to Context
  2. A class can exist in either space if it does not reference any class in the other space. For example, a utility class with all static methods like StringUtils can be loaded once in the persisted space, and loaded separately in the reloadable space.

So you can see that the rules are not very restrictive. Except for the crossing classes that have objects referenced across the two spaces, all other classes can be freely used in either the persisted space or the reloadable space or both. Of course, only classes in the reloadable space will enjoy being reloaded with reloading cycles.

So the most challenging problem with class reloading is dealt with. In the next example, we will try to apply this technique to a simple web application, and enjoy reloading Java classes just like any scripting language.

Example 5: Little Phone Book

Here’s the source code..

This example will be very similar to what a normal web application should look like. It is a Single Page Application with AngularJS, SQLite, Maven, and Jetty Embedded Web Server.

Here is the reloadable space in the web server’s structure:

A thorough understanding of the reloadable space in the web server’s structure will help you master Java class loading.

The web server will not hold references to the real servlets, which must stay in the reloadable space, so as to be reloaded. What it holds is stub servlets, which, with every call to its service method, will resolve the actual servlet in the actual context to run.

This example also introduce a new object ReloadingWebContext, which provides to the web server all values like a normal Context, but internally holds references to an actual context object that can be reloaded by a DynamicClassLoader. It is this ReloadingWebContext which provide stub servlets to the web server.

ReloadingWebContext handles stub servlets to the web server in the Java class reloading process.

The ReloadingWebContext will be the wrapper of the actual context, and:

  • Will reload the actual context when an HTTP GET to “/” is called.
  • Will provide stub servlets to the web server.
  • Will set values and invoke methods every time the actual context is initialized or destroyed.
  • Can be configured to reload the context or not, and which classloader is used for reloading. This will help when running the application in production.

Because it’s very important to understand how we isolate the persisted space and reloadable space, here are the two classes that are crossing between the two spaces:

Class qj.util.funct.F0 for object public F0<Connection> connF in Context

  • Function object, will return a Connection each time the function is invoked. This class resides in the qj.util package, which is excluded from the DynamicClassLoader.

Class java.sql.Connection for object public F0<Connection> connF in Context

  • Normal SQL connection object. This class does not reside in our DynamicClassLoader’s class path so it won’t be picked up.

Summary

In this Java classes tutorial, we have seen how to reload a single class, reload a single class continuously, reload an entire space of multiple classes, and reload multiple classes separately from classes that must be persisted. With these tools, the key factor to achieve reliable class reloading is to have a super clean design. Then you can freely manipulate your classes and the whole JVM.

Implementing Java class reloading is not the easiest thing in the world. But if you give it a shot, and at some point find your classes being loaded on the fly, then you are almost there already. There will be very little left to do before you can achieve totally superb clean design for your system.

Good luck my friends and enjoy your newfound superpower!

Hire a Toptal expert on this topic.
Hire Now
Lê Anh Quân

Lê Anh Quân

Verified Expert in Engineering

Hanoi, Vietnam

Member since August 26, 2014

About the author

Lê has 14 years of experience building web apps, using Java technologies. Over the past 5 years, he has with React and Angular.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

Expertise

PREVIOUSLY AT

FPT Software

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

Join the Toptal® community.