May 14, 2014

Welcome back to our deep-dive into how JRebel works to solve a bunch of issues intrinsic to the nature of Java, such as the way JRebel makes up for the limitations of HotSwap.

If you remember in the first blog post of this series, we sat down with our boss Jevgeni Kabanov, the mad scientist behind the creation of JRebel, to take a look at the problem that exists in Java which has always existed since its creation over 20 years ago -- long reload times of class changes. In this post, we'll take a look at the tools at our disposal to overcome this problem looking at how they work under the covers, including HotSwap, which, introduced in 2001, was barely beneficial to handle the plethora of code changes that developers made back then...and why JRebel was created to pick up the rest of the slack.

The HotSwap Solution

HotSwap goes about a reload by redefining the existing Java class that has been loaded. This means handling updates, such as to instanceKlass in Figure 1, and other aspects including the virtual method table (vtable) and interface method table (itable). You’ll see from the HotSpot class layout, Figure 1, that this will get tricky quickly, as there will be specific solutions for different JVM implementations.

Figure 1. HotSpot Class Layout

Also, different garbage collectors and heap layouts, heap fragmentation rules will also have implications on how redefining a class should be done. This is getting complex extremely quickly.

How JRebel is a Better Version of HotSwap

JRebel is different from any productivity of its kind. It instruments the application and JVM classes to create a layer of indirection. In the case an application class is loaded, all method bodies will have a redirection using the runtime redirection service, as shown in Figure 2. This service manages and loads the class and method versions using anonymous inner classes created for each version that is reloaded. Let’s look at an example. We’ll create a new class C with two methods:

public class C extends X { int y = 5; int method1(int x) { return x + y; } void method2(String s) { System.out.println(s); } }

When Class C is loaded for the first time, JRebel instruments the class. The signature of this class will be the same, but the method bodies are now being redirected. The loaded class will now look something like this:

public class C extends X { int y = 5; int method1(int x) { Object[] o = new Object[1]; o[0] = x; return Runtime.redirect(this, o, "C", "method1", "(I)I"); } void method2(String s) { Object[] o = new Object[1]; o[0] = s; return Runtime.redirect(this, o, "C", "method2", "(Ljava/lang/String;)V"); } }

To the redirect calls, we passing in the calling object, the parameters to the method that has been called, our class name, our method name and the types of the parameters and return. JRebel also loads a class with the implementations at a specific version, initially version 0. Let’s see what that looks like:

public abstract class C0 { public static int method1(C c, int x) { int tmp1 = Runtime.getFieldValue(c, "C", "y", "I"); return x + tmp1; } public static void method2(C c, String s) { PrintStream tmp1 = Runtime.getFieldValue( null, "java/lang/System", "out", "Ljava/io/PrintStream;"); Object[] o = new Object[1]; o[0] = s; Runtime.redirect(tmp1, o, "java/io/PrintStream;", "println","(Ljava/lang/String;)V"); } }

Let’s now say the user changes their class C by adding a new method z() and invoking it from method1 . Class C now looks like this:

public class C { int y = 5; int z() { return 10; } int method1(int x) { return x + y + z(); } ... }

The next time the runtimes uses this class, JRebel detects there is a newer version that has been compiled and on the filesystem, so it loads the new version, C1. This version has the additional method z and the updated implementation for method1.

public class C1 { public static int z(C c) { return 10; } public static int method1(C c, int x) { int tmp1 = Runtime.getFieldValue(c, "C", "y", "I"); int tmp2 = Runtime.redirect(c, null, "C", "z", "(V)I"); return x + tmp1 + tmp2; } ... }

The Runtime.redirect call will always be routed to the latest version of the class C, so calling new C().method1(10) would return 15 before the code change and 25 afterwards. This implementation misses a lot of detail and optimizations, but you get the idea.

Final Thoughts on HotSwap

Without HotSwap's significant limitations on the types of class changes developers frequently need to reload dynamically, it's possible that JRebel would never have been created. This article shows you a bit of what JRebel does behind the scenes to speed up things a bit for modern Java EE development, where things can get very complicated -- especially when you want to extend your stack outside of Java EE and bring in some additional libraries/frameworks. This is what we get into next time, how to make it all work with 50 or so frameworks and technologies that certainly don't play nice right out of the box. Got comments? Tweet me at @sjmaple! :-)

Looking for additional insight into HotSwap and JRebel? Our article on reloading Java classes is a great read.

Read the Article