Skip navigation

In my spare time I have been working on fakereplace, a project that aims to provide more fine grained hot deployment in java that what is currently availible.

 

Currently hot deployment of code changes is done one of two ways, either by discarding the applications ClassLoader(s) and reloading the application, or by using JVM HotSwap.

 

Discarding the ClassLoader and then re-loading all application classes works quite well for small projects, however for large applications it is often not as effective. For bigger projects the time spent initialising the application is often much greater than the amount of time the application server takes to load, so the time saving is quite small. Discarding the old application can also result in significant garbage collection overhead, so in some cases a hot deployment can actually be slower than an application server restart.

 

HotSwap on the other hand works pretty much instantly, however it is quite limited in the changes it can make. It does not allow changes to the schema of the class, only the bytecode in method bodies. This can be quite a time saver in some situations, however it is certainly not a complete solution.

 

Fakereplace utilises a different approach, it works by transforming the bytecode of classes as they are loaded into the JVM in order to make them hot reloadable, and then it uses the java.lang.instrument api to hot swap the classes (this API allows you to redefine classes at runtime, with the same limitations at HotSwap). The modified classes are recompiled to that they appear to have been hot redeployed, even though the class schema has not changed.

 

Currently Fakereplace works with Jboss Seam 2.x, and is under heavy development. If you want to try it out it can be found here. If you would like to learn more about how it works, read on.

 

Suppose we have the following class:
class Class1
{
 protected static int field;
}
After we have deployed we realise that we left off getter and setter methods so we want to replace the class with the following:
class Class1
{
  protected static int field;
 
  public static int getField()
  {
     return field;
  }
 
  public static void setField(int val)
  {
     field = val;
  }
}
In order to achieve this transformation we modify the original Class1 as it is loaded so it looks like this:
class Class1
{
  protected static int field;
  
  public static Object __REDEFINED_STATIC_METHOD_(int methodNo, Object[] parameters)
  {
    return null;
  }
}
This transformation is applied to all redeployable classes as they are loaded, due to some instrumentation magic this method is not visible to reflection calls.
The basic idea is that we are going to take the bytecode from our added getField method, assign it a method number, and stick it into our added method, the end result will look something like this:
class Class1
{
  protected static int field;
  
  public static Object __REDEFINED_STATIC_METHOD_(int methodNo, Object[] parameters)
  {
    if(methodNo == 0)
    {
      return field;
    }
    if(methodNo == 1)
    {
      Integer p0 = (Integer)parameters[0];
      int unboxedP0 = p0.intValue();
      field = unboxedP0;
      return null;
    }
    return null;
  }
}
So now we have our bytecode in the JVM, however we still have quite a few things left to do:
•     we need to recompile all new code that is loaded to refer to the redefined method
•     we need to instrument the reflection API so that getMethods() etc return the expected results.
In order to make this easier we introduce an invoker object. This object serves several purposes:
•     handle boxing/unboxing of parameters/return values
•     stuffs the parameters into the object array
•     provides an actual Method object we can return from getMethod and other reflection calls
The code for the setter invoker looks something like this:
public class FakeObject1
{
  public void setField(int param)
  {
    Object[] params = new Object[1];
    params[0] = new Integer(param);
    Class1.__REDEFINED_STATIC_METHOD_(1,params);
  }
}
Now any code being loaded into the JVM that refers to Class1.setField is changed to refer to FakeObject1.setField, which is a relatively easy transformation to do.
We still have not dealt with the reflection API. In order to do this we need to modify classes as they are loaded to point to reflection wrappers, instead of the actual methods. For example
Class.getMethods()
will get re-written to the static method
ReflectionDelegate.getMethods(Class clazz);
This method knows about our added methods, and will return Method objects for FakeObject1.setField, as well as hiding our added methods. This also allows for annotations to be changed, as calls such as Class.getAnnotations() are also modified to return updated annotation information for redefined classes.