JRebel Alternative: Feenix 2.2 beta is ready!

Newcomers to this blog may not know about Discotek.ca’s Feenix project, but will almost certainly have heard about JRebel, the class and framework reloading software. Discotek.ca’s original class reloading project, Feenix, used the Instrumentation API and was vastly inferior to JRebel (further discussion here). However, Feenix has been entirely re-written and now supports class reloading in a fashion similar to JRebel. Further, Feenix is free and the first beta version is now available!

Before I go any further, I’d like to give praise to the original author of JRebel, Jevgeni Kabanov. As I developed this new version of Feenix, I had to overcome many complex problems. Some of these problems were so complex, that I only persevered to find solutions because I knew they had already been solved by others. Not only did the Jevgeni conceive the JRebel approach to class reloading, but solved each problem without the certainty it could be done. Needless to say, I am impressed, but then again, I am no PhD either.

The title of this blog is JRebel Alternative…, but JRebel does much more than Feenix. For now, Feenix just does class reloading; it does not reload resource bundles or frameworks. Adding support for resource bundles should not be difficult, but the thought of supporting every version of every framework is daunting. I’ll probably just add support for major frameworks like Spring, JSF, etc first (although, I am sure this is more easily said than done). Feenix also doesn’t handle anonymous inner classes (but until it does, this issue can be overcome by giving your inner classes a name). On the other hand, Feenix does support the following:

  1. Basic class reloading
  2. Provide access to fields in an object that only exist in future versions of the class used to instantiate the object (kinda mind blowing, right?)
  3. Invoke methods on an object that did not exist in the version of the class used to instantiate the object (again, very cool – am I right?)
  4. A remote class loading feature similar to the now defunct LiveRebel

The rest of this blog is going to explain a little about the magic behind bullets 2 and 3 in the preceding list, explain how to use Feenix, and show you where to download it. BTW, the first bullet above has already been covered in a previous blog post.

Adding Future Fields to a Class/Instance

By revealing this trick, I actually feel a bit like a magician who explains how to cut someone in half. Its actually really simple and you will roll your eyes afterwards. Every type in Java can be referenced as either a primitive or a java.lang.Object and every field in a class has a unique name. If we create a class with accessors for any type of value or object, which stores these values or objects in an internal Map, you have a construct that can store values and objects that may be defined in future versions of a class. Here is what my implementation looks like:

public class PhantomFieldHolder {

    public static final byte DEFAULT_BYTE_VALUE = 0; 
    public static final short DEFAULT_SHORT_VALUE = 0; 
    public static final int DEFAULT_INT_VALUE = 0;
    public static final long DEFAULT_LONG_VALUE = 0L; 
    public static final float DEFAULT_FLOAT_VALUE = 0.0f; 
    public static final double DEFAULT_DOUBLE_VALUE = 0.0d; 
    public static final char DEFAULT_CHAR_VALUE = '\u0000';
    public static final boolean DEFAULT_BOOLEAN_VALUE = false; 

    Map map = new HashMap();

    public void setBoolean(String name, boolean value) {
        map.put(name, value);
    }

    public boolean getBoolean(String name) {
        Boolean value = (Boolean) map.get(name);
        return value == null ? DEFAULT_BOOLEAN_VALUE : value;
    }

    public void setByte(String name, byte value) {
        map.put(name, value);
    }

    public byte getByte(String name) {
        Byte value = (Byte) map.get(name);
        return value == null ? DEFAULT_BYTE_VALUE : value;
    }

    public void setShort(String name, short value) {
        map.put(name, value);
    }

    public short getShort(String name) {
        Short value = (Short) map.get(name);
        return value == null ? DEFAULT_SHORT_VALUE : value;
    }

    public void setInt(String name, int value) {
        map.put(name, value);
    }

    public int getInt(String name) {
        Integer value = (Integer) map.get(name);
        return value == null ? DEFAULT_INT_VALUE : value;
    }

    public void setFloat(String name, float value) {
        map.put(name, value);
    }

    public float getFloat(String name) {
        Float value = (Float) map.get(name);
        return value == null ? DEFAULT_FLOAT_VALUE : value;
    }

    public void setDouble(String name, double value) {
        map.put(name, value);
    }

    public double getDouble(String name) {
        Double value = (Double) map.get(name);
        return value == null ? DEFAULT_DOUBLE_VALUE : value;
    }

    public void setLong(String name, long value) {
        map.put(name, value);
    }

    public long getLong(String name) {
        Long value = (Long) map.get(name);
        return value == null ? DEFAULT_LONG_VALUE : value;
    }

    public void setObject(String name, Object value) {
        map.put(name, value);
    }

    public Object getObject(String name) {
        return map.get(name);
    }
}

You must also instrument every class (within the specified class reloading namespace) such that it has two PhantomFieldHolder fields (one each for static and non-static fields). The rest of the trick involves instrumenting the byte code of any class that accesses these fields. If the field is defined in a give class, access it normally. Otherwise, access it through the PhantomFieldHolder.

Adding Future Methods to Class/Instance

There is even less magic to adding future methods. We have already seen half of this trick in a previous blog. Specifically, every method is instrumented such that when invoked, it checks to see if there is an updated version of itself. If there is, a special version of the updated class is generated, which implements an interface the original class is aware of (it contains a known invoke(…) method). The original class forwards execution via the invoke method. If there is no updated class, it simply continues execution as it normally would.

Clever readers may ask, what happens when newer code wants to invoke new methods on an old object? For example, if you instantiated an instance of Car with only an accelerate() method, but later updated that class definition such that it also has a brake() method, how can you invoke the new brake() method on the original Car object? The answer here is similar to above in access fields. Classes must be instrumented such that when invoking a method in a reloadable class, if the method does not exist in the class you are invoking, it must exist in a newer version of the class. Hence, just as above, generate the new special version of the newer class and invoke its invoke(…) method with the appropriate parameters. Admittedly, I am making it sound easier than it is and I am not sure I implemented this functionality 100% correctly, but it seems to work well in my testing thus far.

Configuring Feenix

Standalone Mode

In standalone mode, Feenix will reload classes of a given namespace (e.g. com.example.*) from a provided repository of classes. For now, the repository must be a file system directory. In the near future, jar/zip, war, and ear respositories will likely be added.

Feenix is a Java agent and is integrated using the -javaagent JVM parameter. The syntax is as follows:

java -javaagent:<path to Feenix agent jar>=<path to Feenix configuration file> [-noverify] <program> <program parameters>

In case the syntax is at all complicated, I’ll provide a real example. I built a simple text editor in Swing to help test Feenix. It is bundled with the Feenix distribution, which looks like this:

Let’s extract the distribution to /java/feenix/. Next, for the sake of this example, let’s assume you set up your IDE to output class files to /java/projects/feenix-editor/bin. To run the editor’s main class, ca.discotek.feenixtest.FeenixTestGui, you would invoke:

java -classpath /java/projects/feenix-editor/bin ca.discotek.feenixtest.FeenixTestGui

Before we can add Feenix agent parameters to this command line, we need to create a configuration file. Let’s create file /java/project/feenix-editor/feenix.properties. In this file we’ll add the following properties:

  1. project-name=testgui
  2. feenix_enabled=true
  3. class_inclusion=ca\.discotek\.feenixtest.*
  4. feenix_classpath_entry=/java/projects/feenix-editor/bin

The project-name property is used by Feenix internally (But make sure the value consists of valid characters for your file system. You may get unexpected results if you use a slash, colon, question mark, etc). The feenix_enabled property allows you to turn off Feenix functionality without having to modify the Feenix configuration or program command line. You may have multiple class_inclusion=… definitions. This property’s value is a Java regular expression which represents the namespace of the classes you wish to reload. The feenix_classpath_entry property defines a directory location where Feenix can find new versions of your classes. You may also have multiple feenix_classpath_entry definitions. Not shown above is the class_exclusion property. It is used to exclude class from the included set.

To invoke the program with Feenix configured, you invoke:

java -classpath /java/projects/feenix-editor/bin -noverify -javaagent:/java/feenix/discotek.feenix-agent-2.2-beta.jar=/java/project/feenix-editor/feenix.properties ca.discotek.feenixtest.FeenixTestGui

You’ll notice in the syntax above there is an optional -noverify JVM parameter. If you want to have reloadable constructors, you will need to use the -noverify flag. The problem stems from Feenix needing to insert code before a constructor’s required call to super(…). Specifically, Feenix must insert the if there is a newer version of this class, forward the execution etc code ahead of the call to super(…). Inserting instructions in this manner will cause JVM verification errors at run-time. If you don’t include the -noverify parameter, Feenix will not add the required code to reload constructors.

Note, the GUI for the Remote Mode below comes with an editor for creating configuration files.

Remote Mode

Feenix allows you to develop your code on one machine and execute it on another (this is similar to JRebel’s LiveRebel software, for which they have discontinued support). To configure and run the server that provides your newly developed classes, run one of the following commands:

java -jar /java/feenix/discotek.feenix-gui-2.2-beta.jar

or

java -classpath /java/feenix/discotek.feenix-gui-2.2-beta.jar ca.discotek.feenix.gui.FeenixProjectManagerGui

Here is a screen shot of the server GUI:

Let’s create a new project and configure it to serve classes to remote clients. First, click the New… button to enter a new project name:

Next, let’s click the Class Inclusions Add… button:

This dialog may provide two options for adding inclusions: Manual Edit and Select from Running JVM. The Manual Edit option is always available, but the Select from Running JVM is only available if you are running ca.discotek.feenix.gui.FeenixProjectManagerGui from a JDK JRE and its <jdkhome>/lib/tools.jar file is on the classpath. You need to add it to the classpath yourself. The command line examples above do not include tools.jar.

If you select the Manual Edit option, you’ll be prompted with another dialog with a text field in which you can enter the class inclusion regular expression. If you select the Select from Running JVM option, you’ll be presented with a dialog that allows you to select a package from the class namespace of a running JVM. This is a convenience feature to help speed up configuration time and eliminate the errors that might occur while manually editing the regular expression. Be sure to have your target application running in a JVM before launching the dialog (of course if the target JVM is only running remotely, this feature may not be very helpful). In the following dialog…

…I have three Java processes running. 1884 is the FeenixProjectManagerGui class we are currently using to create a configuration. 1472 is the JVM running an instance of Eclipse. 1904 is the target JVM running the simple editor test code. When the 1904 process is selected, the JVM’s loaded classes will be displayed. The following dialog…

…shows you the package path expanded such that ca.discotek.feenixtest can be selected and the field below it shows the regular expression to be used as an inclusion. Note, you can select multiple packages and/or classes. Once you click Okay, the regular expression(s) will show up in the inclusion table.

You can edit exclusions in the exact same manner as inclusions.

To add a classpath entry, where Feenix will discover your new classes, click the Classpath Add… button. You will be presented with a dialog similar to the following, in which you can manually type the file system path or select it with a file system browser:

Click the Okay button and our Feenix configuration editor will look similar to this:

Next, let’s look at the Remote tab:

The Host Name field is used to enter the host address that the local ServerSocket will bind to. The Port value is the port the ServerSocket will bind to. The Poll Frequency field allow you to configure out often Feenix will poll the classpath for updates. The Feenix server functionality is implemented as follows:

  1. When the server is started, it will bind to the address and port as specified above.
  2. It will then wait for clients to connect to the server.
  3. The server will poll the classpath for updates at the specified frequency.
  4. When updates are discovered, they will be propagated to each client.

You can start the server by clicking the Start button in the GUI editor:

Once the server has started, you can connect a client. A client is simply the target JVM configured to use Feenix, but has an additional JVM property, feenix-remote-enabled, which explicitly tells Feenix, it should find its new classes via a remote server. The following shows how the above standalone configuration command line would be modified to find classes remotely:

java -classpath /java/projects/feenix-editor/bin -noverify -Dfeenix-remote-enabled=true -javaagent:/java/feenix/discotek.feenix-agent-2.2-beta.jar=/java/project/feenix-editor/feenix.properties ca.discotek.feenixtest.FeenixTestGui

In the standalone configuration, the configuraton file requires properties for the location of new classes and properties for their inclusion and exclusion. In a remote configuration, the remote client doesn’t need these properties. It just needs the properties for discovering the server. The following is adequate:

address=my-server-address
port=8888
feenix_enabled=true

However, if you wanted to use the server configuration file to configure the remote client, you can. The remote client will simply ignore polling frequency, classpath, inclusion, and exclusion properties when in remote mode. To this end, you can export the server configuration (File->Export Configuration…) for use on a remote client.

Final Notes

If you download Feenix and use its classloading functionality, I have some notes that may be worth reading first.

  • I’d love to hear your feedback. You can do that here: https://discotek.ca/contact.xhtml
  • In hindsight, the class inclusion and exclusion properties are probably not necessary and will likely disappear in future versions. Be sure to let me know if you think they are useful.
  • If you want to use the example code provided with the distribution to evaluate Feenix, I suggest you comment out everything except the main method of FeenixTestGui. From there, uncomment chunks of code and recompile to see Feenix’s reload functionality in action.
  • This software has a built-in expiry date of 6 months from the date it was built. This is to ensure that no beta versions of Feenix are kept around. Once the beta is over, there will be no expiry date and builds will never expire (unless there are future beta builds).
  • At this time, I would not recommend using Feenix (standalone or remote mode) in production.
  • Feenix is not open source at this time. It may be in the future, but it will always be free.
  • Feenix only does class reloading at this time (no resource bundles, web frameworks, EJBs etc)
  • At this time, Feenix does not handle anonymous inner classes (just name your inner classes so they aren’t anonymous).
  • When possible, Feenix will attempt to call Instrumentation.redefineClasses(…) to redefine the class for existing objects. This is mostly important for improving the efficiency of accessing fields and methods (i.e. it is more efficient to run classes that don’t require reloading functionality discussed in this article). By default, Feenix does not tell you when this is not possible. If you need more information about when class can and cannot be reloaded, you can add the following JVM parameter: -Dfeenix-verbose=true
  • Stack traces for reloaded classes will look a little strange, but the class names and line numbers they provide will match up with your source code.
  • Remember to use -noverify or otherwise, your constructors won’t be reloaded.
  • Lastly, go easy on me – JRebel has millions of dollars in venture capital and in 2012 had 50-100 developers, but apparently tripled in size in 2015. I am just one dude who starts coding after his wife and kids are asleep at night!

Download

You can download the Feenix 2.2-beta distribution here.

If you liked this article and would like to be notified of future articles or Feenix releases, follow Discotek.ca on twitter.

This entry was posted in Byte Code Engineering, Developer Tool and tagged , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>