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.
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.
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
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.
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 DynamicClassLoader
s and old User
classes will be unlinked and subjected to the garbage collector.
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.
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
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.
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:
- 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 persistedConnectionPool
class, butConnectionPool
has no reference toContext
- 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
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:
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.
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!
Further Reading on the Toptal Blog:
Lê Anh Quân
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.
Expertise
PREVIOUSLY AT