Hot-Swapping Java Class File During Runtime

Hot-swapping class file is a popular technique in Java to provide class file replacement during runtime. This is a handy feature for developers that makes it possible to modify class files and test their functions on the fly without recompiling the whole project. And it also serves as a base function when implementing automatic system upgrade. This passage mainly focuses on the core technique required to acheive class file hot-swapping, the Class Loaders in Java, and a naive implementation of class file hot-swapping.

Java Class Loader

Before we get into the implementation of class file hot-swapping, it’s necessary to have an understanding of class loader technique in Java as it’s the solid pillar underneath the grand hall.

As you may know, Java has a hierarchical class loader architecture, which can be depicted as below.

![Java Class Loader Hierarchy](http://g.gravizo.com/g? digraph G {
BootstrapClassLoader [shape=box]
ExtClassLoader [shape=box]
AppClassLoader [shape=box]
CustomClassLoader [shape=box]
BootstrapClassLoader -> ExtClassLoader
ExtClassLoader -> AppClassLoader
AppClassLoader -> CustomClassLoader
}
)

From top level to the bottom, they’re namely BootstrapClassLoader, ExtClassLoader, AppClassLoader and CustomClassLoader. They’re responsbile for different kinds of class loading tasks. Below demonstrates a general picture.

BootstrapClassLoader: The toppest level class loader, which reads all class files under sun.boot.class.path. This path defaultly includes all core Java APIs under jre/lib directory and all jar files under the directories that are specified using -Xbootclasspath parameter when the program is loaded into JVM. If lower level class loaders can’t find a specified class file, this one would be the last resort by default.

ExtClassLoader: As we know that Java has a variety of extension libraries provided in JRE under jre/lib/ext directory. So in addition to core APIs, JVM will search for this path to load extension jar or class files by default. This task is handled by ExtClassLoader, which will look for class files defined in java.ext.dirs. Also you can provide your own paths that containing your extension class files by specifing the value of -Djava.ext.dirs to overwrite the default one.

AppClassLoader: This class loader takes charge in all class files defined under java.class.path. The default value is defined in CLASSPATH environment variable. And you can modify it either by changing this variable or specify your own with -classpath.

CustomClassLoader: Althogh it’s the bottom level class loader, it’s the most powerful one as you can define your own class loading mechanism here to do whatever you want to do.

In order to load a class, JVM will firstly search the class loader hierarchy from bottom to the top to see if a class has been loaded or not. If it has been loaded, then returns the reference to this class. If all class loaders haven’t loaded this class, JVM will try to load this class from top to bottom. If this fails after trying with all class loaders, an exception will be thrown. Since each class loader has its own namespace, classes with same name can only be loaded once in a class loader.

If we don’t provide our own class loader, our code will be loaded using AppClassLoader by default, which means a class file can only be loaded once despite of any changes on its content. To overcome this, we need to implement our own class loader so that different versions of a same class file can be loaded dynamically.

Customized Class Loader

Althogh customized class loader can be powerful, we still need to obey its rules. Since a class can only be loaded by a class loader only once, in order to load a same class file with different version, we need to create a different class loader. As a result, the default system class loaders can’t be involved in our implementation. And because we want to JVM know that our class loader is a class loader so that it can be used by the JVM to read class files, we also need to extend our class from an abstract class ClassLoader which is provided by Java.

Below is our implementation of customized class loader.

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
public class HotSwapClassLoader extends ClassLoader {
private String baseDir;
private Set<String> classNames;

public HotSwapClassLoader(String baseDir, String[] classNames) throws IOException {
super(null); // Set parent class loader to null since we don't want system class loader to load our class
this.baseDir = baseDir;
this.classNames = new HashSet<>();
loadClass(classNames);
}

// Entry method for loading a class. All explicit class loading is implemented by this method.
@Override
protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class clazz = findLoadedClass(name); // To find if this class has been loaded or not
if(!this.classNames.contains(name) && null == clazz) {
clazz = getSystemClassLoader().loadClass(name); // Delegate class files we're not intrested in to system class loaders
}
if(null == clazz) {
throw new ClassNotFoundException(name);
}
if(resolve) {
resolveClass(clazz); // Link a class file to make sure it's usable
}
return clazz;
}

private void loadClass(String[] classNames) throws IOException {
for(String className : classNames) {
loadDirectly(className);
this.classNames.add(className);
}
}

private void loadDirectly(String name) throws IOException {
StringBuffer sb = new StringBuffer(this.baseDir);
String className = name.replace('.', File.separatorChar) + ".class";
sb.append(File.separator + className);
File classFile = new File(sb.toString());
byte[] raw = new byte[(int)classFile.length()];
InputStream is = new FileInputStream(classFile);
is.read(raw);
is.close();
defineClass(name, raw, 0, raw.length); // Accept a byte array of class data and transform it to a class
}
}

The general idea of this implementation is that we load all requried class files during the instantiation period of our class loader and delegate others to system class loaders. With this in hand, now we’re able to acheive our goal: class file hot-swapping.

Class File Hot-Swapping

In order to make an experiment, we create a simple class file first which will be replaced by newer version later.

1
2
3
4
5
public class Foo { 
public void sayHello() {
System.out.println("Hello world! V1.0");
}
}

We compile this java file first and put it under the resources directory of our project. Then we create a new thread to load that class and invoke its sayHello method every two seconds.

1
2
3
4
5
6
7
8
9
10
try {
HotSwapClassLoader loader = new HotSwapClassLoader("hotswap/src/main/resources/", new String[] {"Foo"});
Class clazz = loader.loadClass("Foo");
Object foo = clazz.newInstance();
Method method = foo.getClass().getMethod("sayHello");
method.invoke(foo);
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}

Note that each time we load this class there’s a new instance of class loader generated first. Because, as is mentioned before, class files with the same qualified name can only be loaded into a class loader once.

Now we run the program and change the version in Foo to V2.0, you will see an output similar to below.

1
2
3
4
5
6
Hello world! V1.0
Hello world! V1.0
Hello world! V1.0
Hello world! V1.0
Hello world! V2.0
Hello world! V2.0

The class Foo has been hot-swapped successfully during runtime.

Notice

That’s it. Althogh our example is quite simple, we have a workable hot-swap class loader now for demonstration purpose. But when considering the production environment, things can be more tricky. Our naive implementation of hot-swap class loader can never be applied onto this environment due to performance issue. As is mentioned earlier, we’ll create a new instance of our class loader every two seconds. This behavior is redundant and a wast of resource even though we enlarge the sleeping cycle. We need to modify it to do the same work only when it is needed. Hence, if you’re desining your production system, you need more work and consideration.

Conclusion

This passage has discussed basic technologies covered in class file hot-swapping in Java. This topic can be very complicated if we involve other aspects like system auto-upgrading/auto-downgrading, and concrete implementation should depend on detailed requirements. Besides above, more details should be digged out by readers yourselves. Let’s keep moving forward!

文章目录
  1. 1. Java Class Loader
  2. 2. Customized Class Loader
  3. 3. Class File Hot-Swapping
  4. 4. Notice
  5. 5. Conclusion
|