Brief introduction
In the Java development field, hot deployment has always been a difficult problem, the current Java virtual machine can only implement the method body modification 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. The advent of the OSGi architecture makes it possible to restart the module, but if there is a call relationship between the modules, such operations will still cause transient functional shock to the application. This article explores how to implement a single-class thermal deployment without destroying the existing behavior of the Java virtual machine, so that the system does not need to reboot to complete a class update.
Exploration of class loading
First of all, what is hot deployment (HOTSWAP), a hot deployment is the ability to automatically detect changes to a class file without restarting the Java virtual machine, updating the behavior of the runtime class. Java classes are loaded by a Java virtual machine, and a class file is ClassLoader loaded, the corresponding class object is generated, and then an instance of the class is created. The default virtual machine behavior only loads the class at startup, and if a later class needs to be updated, simply replacing the compiled class file, the Java virtual machine will not update the running class. If you want to implement a hot deployment, the most fundamental way is to modify the virtual machine's source code, change the load behavior of the ClassLoader, so that the virtual function listens to the class file update, reload the class file, this behavior is very destructive, for the subsequent JVM upgrade 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 when the classes are loaded, allowing for a hot deployment. This article will specifically explore how to implement this scenario. The first step is to understand the existing loading mechanism of the Java virtual machine. The current loading mechanism, called parental delegation, asks the current ClassLoader parent class if it is capable of loading, and if the parent class cannot implement the load operation, the task will be classloader to the ClassLoader to load. The benefit of this top-down loading method is that each classloader performs its own load task and does not load the class repeatedly. But this makes the loading order very difficult to change, so that custom ClassLoader preemptive loading of classes that need to listen for changes becomes a challenge.
But we can change the idea, although we can't get the first load of the class, but you can still create a class with the same functionality as the custom ClassLoader, so that each instantiated object points to the new class. When the class file changes, create an updated class again, and then if the system makes an instantiation request again, the created object points to the new class.
Here's a brief list of what needs to be done.
- 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 the created objects so that they are created with the custom ClassLoader loaded class.
Implementation of the custom loader
The custom loader still needs to perform the function of class loading. There is a problem here, the same ClassLoader cannot load two classes of the same name at the same time, because no matter how the structure of the class 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 little trick to keep each loaded class stored as a type with version information, like 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 that actually executes the load class file to create the class is a native method that becomes difficult to modify. So there is still a road ahead, that is, directly modify the compiled generated class file.
Modifying class files with ASM
There are many frameworks that can modify bytecode, such as Asm,cglib. This article uses ASM. First to introduce the structure of class file, class file contains the following types of information, one is the basic information of the class, including access rights information, class name information, parent information, interface information. The second one is the variable information for the class. The third one is information about the method. ASM loads a class file and then reads the class information in strict order, and the user can define the enhanced component to modify the information and then output it as a new class.
First look at how ASM can be used to modify class information.
Listing 1. Using ASM to modify byte codes
Classwriter cw = new Classwriter (classwriter.<em>compute_maxs</em>); 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 the process of modifying bytecode files is a chain of responsibility mode, first using a classreader to read the bytecode, and then use Classvisitor to make personalized changes, and finally use Classwriter output modified bytecode.
As mentioned before, the class name of the read class file needs to be modified to be loaded into a derived class with a completely new name. This is divided into 2 steps.
The first step is to turn the original class into an interface first.
Listing 2. Redefining the original class
Public class<? > Redefineclass (String className) { Classwriter cw = new Classwriter (classwriter.<em>compute_maxs</em> ); Classreader CR = null; Classsource cs = <em>classfiles</em>.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); }
First load the class file of the original classes, where an enhanced component ClassModifier
is defined that modifies the type of the original class and transforms it into an interface. All method logic for the original class is removed.
In the second step, the generated derived classes implement this interface, the original class, and copy all the method logic from the original class. Later, if the class needs to be updated, a new derived class is generated, and this interface is implemented. The purpose of this is to have a common interface between the derived classes of the same class, regardless of how they are modified, and the transitions between them become opaque to the outside.
Listing 3. Defining a derived class
Redefine this class private class< when the class file changes;? > Redefineclass (String className, Classsource classsource) {classwriter cw = new Classwriter (classwriter.<em>c ompute_maxs</em>); Classreader CR = null; Classsource.update (); String enhancedclassname = Classsource.getenhancedname (); try {cr = new Classreader (New FileInputStream (Classsource.getfile ())); } catch (IOException e) {e.printstacktrace (); return null; } enhancedmodifier em = new Enhancedmodifier (CW, Classname.replace (".", "/"), Enhancedclassname.re Place (".", "/")); Extendmodifier EXM = new Extendmodifier (em, Classname.replace (".", "/"), Enhancedclassname.replace (".", "/ ")); Cr.accept (EXM, 0); byte[] Code = Cw.tobytearray (); Classsource.setbytecopy (code); class<? > Clazz = DefineClass (enhancedclaSsname, code, 0, Code.length); Classsource.setclasscopy (Clazz); return clazz; }
Load the class file of the original classes again, where two enhancements are defined, one EnhancedModifier
of which is the function of the enhanced component to change the original class name. The second enhancement component is ExtendModifier
that the function of this enhanced component is to change the parent class of the original class so that the modified derived class can implement the same original class (at which point the original class has been turned into an interface).
Custom ClassLoader Another function is to listen to the class file that will change, ClassLoader will manage a timer and periodically scan these class files for changes.
Changing the behavior of creating objects
There are two common ways to create objects in a Java virtual machine, one that is statically created, a direct new object, a dynamic creation, and a reflection method to create an object.
Since the type of the original class has been changed in the custom loader and changed from class to interface, neither of these methods can be created. What we want to do is to instantiate the behavior of the original class into an instantiated derived class.
The first way to do this is to create it statically, change it to get the class by ClassLoader, and create the object dynamically.
Listing 4. The logic corresponding to the replaced instruction set
Original logic Greeter p = new Greeter ();//Changed logic igreeter p = (igreeter) myclassloader.<em>getinstance</em& gt; (). Findclass ("Com.example.Greeter"). newinstance ();
ASM is also needed to modify the class file. Finds statements for all new objects, replaced by ClassLoader to get the form of an object.
Listing 5. Using ASM to modify the method body
@Override public void visittypeinsn (int opcode, String type) {if (opcode==opcodes.<em>new</em> & & Type.equals (ClassName)) {list< Localvariablenode> variables = node.localvariables; String compiletype = null; for (int i=0;i<variables.size (); i++) {Localvariablenode localvariable = Variables.get (i); compiletype = <em >formType</em> (LOCALVARIABLE.DESC); if (MatchType (Compiletype) &&! Valiableindexused[i]) {Valiableindexused[i] = true; break;}} MV.VISITMETHODINSN (OPCODES.<EM>INVOKESTATIC</EM>, <em>classload_type</em>, "GetInstance" , "() L" +<em>classload_type</em>+ ";"); MV.VISITLDCINSN (Type.replace ("/", ".")); MV.VISITMETHODINSN (OPCODES.<EM>INVOKEVIRTUAL</EM>, <em>classload_type</em>, "FindClass", "(ljava/lang/string;) ljava/lang/class;"); MV.VISITMETHODINSN (Opcodes.<em>invokevirtual</em>, "Java/lang/class", "Newinstance", "() Ljava/lang/Object; "); MV.VISITTYPEINSN (OPCODES.<EM>CHECKCAST</EM>, compiletype); Flag = true; } else {mv.visittypeinsn (opcode, type);} }
For the second method of creation, you need to modify Class.forName()
ClassLoader.findClass()
the behavior of the and so that they load the class through the custom loader.
Using javaagent to intercept the behavior of the default loader
The previously implemented ClassLoader has solved the functionality required for hot deployment, but when the JVM starts, it does not load all the class files under Classpath with a custom loader, instead of loading them with the application loader. If you later load the loaded class with a custom loader, you may have linkageerror exception. You must re-replace the loaded class before the app starts. If there is only one method to use before jdk1.4, change the loading behavior of ClassLoader in the JDK to point to the load behavior of the custom loader. Fortunately, after jdk5.0, we have another aggressive smaller approach, this is the Javaagent method, Javaagent can be launched after the JVM, the application before the start of a short gap, provide space for the user to do some special behavior. The more common application is to use javaagent to do aspect-oriented programming, and to add monitoring logs to the methods.
The implementation of Javaagent is easy, as long as within a class, define a Premain method.
Listing 6. A simple javaagent.
public class Reloadagent {public static void Premain (String Agentargs, Instrumentation inst) { Generaltransformer trans = new Generaltransformer (); Inst.addtransformer (trans); } }
Then write a manifest file that Premain-Class
sets the property to define a premain
class name that owns the method.
Generate a jar package containing this manifest file.
manifest-version:1.0 premain-class:com.example.reloadagent can-redefine-classes:true
Finally, add the parameters to the application's parameters -javaagent
to add the jar. You can also Javaagent
add parameters to the parameter, which is the absolute path to test project in the testing code. This will take precedence over the logic in the method before executing the application, premain
and pre-parse the class that needs to be loaded.
Figure 1. Increase execution parameters
JavaAgent
the substitution of the original bytecode is used here to prevent the original bytecode from being loaded by the Java virtual machine. Simply implement 一个 ClassFileTransformer
the interface and use this implementation class to complete the function of class substitution.
Listing 7. Replace class
@Override public byte [] transform (ClassLoader paramclassloader, String paramstring, class<? > Paramclass, Protectiondomain paramprotectiondomain, Byte [] paramarrayofbyte) throws Illegalclassformatexception {String className = Paramstring.replace ("/", "."), if (Classname.equals ("Com.example.Test")) {Myclassloader CL = Myclassloader.<em>getinstance</em> (); Cl.definereference (ClassName, "Com.example.Greeter"); Return Cl.getbytecode (ClassName); }else if (classname.equals ("Com.example.Greeter")) {Myclassloader cl = myclassloader.<em>getinstance</em > (); Cl.redefineclass (ClassName); Return Cl.getbytecode (ClassName); } return null;
At this point, all the work is done, and enjoy the results of hotswap.
Figure 2. Test Execution Results
Conclusion
Solving HotSwap is a difficult task, this article only solves the new instantiation of the object to use the new logic, and can not change the behavior of the instantiated object, if the JVM can redesign the life cycle of the class, support the runtime to re-update a class,hotswap will become Java A shiny new feature. The official JVM has not been able to solve the problem of hot deployment, and may also be unable to completely overcome the difficulties, I hope that the future JDK will solve this problem, so that Java applications to update more friendly, to avoid constantly restarting the application wasted time.
Deep Exploration of Java thermal deployment