Brief introduction
In the Java development field, hot deployment has always been a difficult problem, the current Java virtual machine can only implement the modification of the method body hot deployment, for the entire class structure modification, still need to restart the virtual machine, the class reload to complete the update operation. For some large applications, each reboot will cost a lot of time. Although the presence of OSGi architectures makes it possible for modules to be restarted, such operations can still cause transient functional shock in applications if there is a call between the modules. This article explores how to implement a one-class hot deployment without disrupting the existing behavior of the Java virtual machine so that the system completes a class update without restarting.
The exploration of class loading
First, let's talk about hot deployment (HOTSWAP), which is the ability to automatically detect changes in class files and update run-time class behavior without restarting the Java virtual machine. Java classes are loaded through a Java virtual machine, and a class file that is loaded by ClassLoader generates a corresponding class object, and then an instance of the class can be created. The default virtual machine behavior only loads the class at startup, and if a later class needs to be updated, simply replace the compiled class file, and the Java virtual machine will not update the running class. If you want to achieve hot deployment, the most fundamental way is to modify the source code of the virtual machine, change the loading behavior of the ClassLoader, so that the virtual function listening to the class file update, reload class file, such behavior is very destructive, for subsequent JVM upgrades buried a big pit.
Another friendly approach is to create your own classloader to load the class that needs to be monitored, so that you can control the timing of classes loading to achieve hot deployment. This article will specifically explore how to implement this scheme. First you need to understand the existing loading mechanism of the Java virtual machine. The current loading mechanism, called parental delegation, when the system uses a classloader to load a class, asks the current ClassLoader whether the parent class is capable of loading, and if the parent cannot implement the load operation, the task is delegated to the ClassLoader to load. The advantage of this top-down loading approach is that each classloader performs its own load task and does not repeat the load class. However, this approach makes the loading sequence very difficult to change, allowing custom ClassLoader to preempt classes that require monitoring changes becomes a challenge.
But we can change the idea that although we can't load the class first, we can still use custom ClassLoader to create a class with the same functionality, so that each instantiated object points to the new class. When this class file changes, create an updated class again, and then if the system sends out an instantiated request again, the object that is created speaks to the new class.
Here's a quick list of the things you need to do.
Create a custom ClassLoader, load the class that needs to listen for changes, and reload the class file when it changes.
Change the behavior of creating objects so that they are created with a custom ClassLoader loaded class.
Implementation of custom Loader
The custom loader still needs to perform the function of class loading. There is a problem here, the same class loader cannot load two classes with the same name at the same time, because regardless of how the class structure changes, the generated class name does not change, and ClassLoader can only destroy the loaded class before the virtual machine stops, so ClassLoader cannot load the updated class. Here's a trick to keep each loaded class as a class with version information, such as when loading Test.class, the class stored in memory is Test_v1.class, and when the class changes, the reload class name is Test_v2.class. But the DefineClass method of actually performing loading class file creation class is a native method, and it becomes difficult to modify. So there is still a road left, that is, directly modify the build generated class file.
Modifying class files with ASM
There are a number of frames that can modify bytecode, such as Asm,cglib. The ASM is used in this article. First to introduce the class file structure, class file contains the following types of information, one is the basic information of the class, including access rights information, class name information, parent class information, interface information. The second is the variable information for the class. The third is the information of the method. ASM first loads a class file and then reads the class's information in strict order, and the user can define the enhanced component to modify the information as he wishes, and finally output it as a new class.
First look at how to use ASM to modify class information.
Listing 1. Using ASM to modify byte code
Classwriter cw = new Classwriter (CLASSWRITER.COMPUTE_MAXS);
Classreader CR = null;
String enhancedclassname = Classsource.getenhancedname ();
try {
cr = new Classreader (new FileInputStream (
classsource.getfile ());
} catch (IOException e) {
E.printstacktrace ();
return null;
}
Classvisitor CV = new Enhancedmodifier (CW,
classname.replace (".", "/"),
enhancedclassname.replace (".", "/" ));
Cr.accept (CV, 0);
ASM modifies bytecode file's process is a responsibility chain pattern, first uses one classreader to read into the bytecode, then uses the classvisitor to make the personalization modification, finally utilizes the Classwriter output to revise the byte code.
As mentioned earlier, you need to make some modifications to the class name of the read class file, and load it into a new name derived class. This is divided into 2 steps.
The first step is to first turn the original class into an interface.
Listing 2. The original class that was redefined
Public class<?> Redefineclass (String className) {
Classwriter cw = new Classwriter (CLASSWRITER.COMPUTE_MAXS) ;
Classreader CR = null;
Classsource cs = classfiles.get (className);
if (cs==null) {return
null;
}
try {
cr = new Classreader (New FileInputStream (Cs.getfile ());
} catch (IOException e) {
e.printstacktrace ();
return null;
}
ClassModifier cm = new ClassModifier (CW);
Cr.accept (cm, 0);
byte[] Code = Cw.tobytearray ();
Return DefineClass (className, code, 0, code.length);
}