How JRebel can speed up your Java application development
JRebel is a productivity tool that allows developers to reload code changes instantly. It skips the rebuild, restart and redeploy cycle common in Java development. JRebel enables developers to get more done in the same amount of time and to stay in the flow while coding. What is even better, JRebel supports the majority of real-world enterprise Java stacks and is easy to install into your development environment.
Let's look at the problem first. When dealing with Java application development, you go through several actions over and over again. The same is generally true for any technology stack. To implement a change in the app, you first need to code it up, then run the application and verify that the changes you just implemented are actually what you wanted them to be -- functionally correct and working. The problem is that the latter can take quite some time, especially with the Java stack. You typically need to build your project, either fully or partially, tear down and restart the Java process running your code and load or deploy your application again.
Two of these phases are irrelevant to implementing the changes. Building, packaging and deploying is not necessary -- rather an artifact of the platform than a necessity. It would be much faster and easier if we could just reload the classes in the JVM with the new class definitions. This way, when the JVM accesses the classes or objects of the changed classes next time, new code would already be in there.
In reality, achieving this is way more complicated than it sounds.
The problem with reloading Java code
All Java code is associated with methods contained in classes. Simplified, you can think of a class as a collection of methods that receive “this” as the first argument. The class with all its methods is loaded into memory and receives a unique identity. In the Java API this identity is represented by an instance of java.lang.Class.You can access it by using the MyObject.class expression.
Every created object gets a reference to this identity, accessible through the Object.getClass() method. When a method is called on an object, the JVM consults the class reference and calls the method of that particular class. That is, when you call mo.method() (where mo is an instance of MyObject), the JVM will call mo.getClass().getDeclaredMethod("method").invoke(mo) (this is not what the JVM actually does, but you can think of it like this).
Once a Java class has been loaded by a classloader, it is immutable and will last as long as the classloader itself. The identity of a class is the class name and classloader identity. To reload an application, you will actually need to create a new classloader, which in turn will load the latest version of the app classes. You cannot map an existing object onto a new class, so it is important to migrate the state through reloads. This could mean recreating the whole application object graph by reinitializing the application, the configuration state etc., or copying over the user session state. This is very often time consuming and quite vulnerable to memory leaks. This is why you cannot just redeploy your application in the application server again and again all day. It will crash with the out of memory errors.
HotSwap and JRebel
Since the problem is quite apparent, especially when you work with larger applications, it received some attention a long time ago. In 2002, Sun introduced a technology into the Java 1.4 JVM, called HotSwap. It was incorporated within the Debugger API, and allowed debuggers to update class bytecode in place, using the same class identity. This meant that all objects could refer to an updated class and execute new code when their methods were called, preventing the need to reload a container whenever class bytecode was changed.
Unfortunately, this redefinition is limited only to changing method bodies. It cannot add methods or fields or otherwise change anything else, except for the method bodies. This limits the usefulness of HotSwap severely. And enhancing HotSwap to work on the other changes beyond the method bodies only is a hard challenge.
It is because the JVM is a heavily optimized piece of software, running on many platforms. Performance and stability are the highest priorities. To support them in different environments the JVM features:
- Two heavily optimized Just-In-Time compilers (-client and -server)
- Several multi-generational garbage collectors
These features make evolving the class schema (adding or changing fields or methods, or changing the class hierarchy) a considerable challenge. When loaded into the JVM, an object is represented by a structure in memory, occupying a continuous region of memory with a specific size - its fields and the metadata. In order to add a field, we would need to resize that structure. But since nearby regions may already be occupied, we would need to relocate the whole structure to a different region where there is enough free space to fit it in. Now, since we’re actually updating a class (and not just a single object) we would have to do this to every object of that class.
JRebel is much more powerful than HotSwap. It can reload the code that contains all kinds of changes to the Java code. It supports adding and removing static or instance fields in the objects, adding, removing, or changing method bodies and signatures, constructors, annotations, enum values, and even changing the implemented interfaces or the class hierarchy.
It is possible because JRebel works on a different level of abstraction than HotSwap. Whereas HotSwap works at the virtual machine level and is dependent on the inner workings of the JVM, JRebel makes use of two remarkable features of the JVM — abstract bytecode and classloaders. Classloaders allow JRebel to recognize the moment when a class is loaded, then translate the bytecode on-the-fly to create another layer of abstraction between the virtual machine and the executed code.
When a classloader tries to load a class, JRebel intercepts that process and modifies the class into a combination of a proxy class and a number of support classes that represent the versions of the class as it goes through the changes due to the app development.
What makes the real difference is that the system is heavily optimized for the JIT transformation runtime and allows modifications to take place without any visible degradation in performance or compatibility. In a nutshell, JRebel:
- Leaves as many method invocations intact as possible. This means that JRebel minimizes its performance overhead, making it lightweight.
- Avoids instrumenting the Java SDK except in a few places that are necessary to preserve compatibility.
- Tweaks the results of the Reflection API, so that we can correctly include the added/removed members in these results. This also means that the changes to Annotations are visible to the application.
Framework support and reloading configuration changes
Nowadays, applications are not just classes and resources; they are wired together by extensive configuration and metadata of the frameworks in use. When that configuration changes it should be reflected in the running application. However it’s not enough to make the changes to the configuration files visible, the specific framework must reload it and reflect the changes in the application as well.
JRebel recognizes the problem and contains integrations with several dozens of frameworks to instrument their lifecycle and support changing the configuration and the components of the framework on-the-fly, without restarting the Java process or reinitializing the whole framework configuration. For example, it allows JRebel to reconfigure or add a Spring framework bean into the running application, or change the Java EE configuration just as easily as changing a class definition.
How does it work in the Eclipse IDE
JRebel itself is just a javaagent, so to instrument your Java process you just need to add an agentpath option to the Java command:
java -agentpath:C:\JRebel\lib\jrebel64.dll com.example.Main
However, since all modern Java development is done from within a smart IDE, it has plugins that you can install which would simplify the configuration of JRebel for your projects.
So you can just install a JRebel plugin from the Eclipse Marketplace and configure it in a couple of clicks.To configure a project, JRebel requires just one configuration file: rebel.xml. It is used by JRebel to map the classes in the running application to the workspace. The good news here is that the IDE plugin can automate the configuration for you. In fact, rebel.xml is generated automatically once you enable JRebel nature for the project.
After that, when you run your project, either as a normal Java process, or on an application server, JRebel will be configured to pick up the changes in your project and reflect them in the running Java process.
Note, that JRebel does not compile Java code on its own. The existing Java tooling doesn't have a problem with compiling the classes. So JRebel relies on the compilation results from the existing Java compiler. For Eclipse users it means that for a smooth user experience with JRebel it is recommended to enable the automatic build feature, also known as compile-on-save.
After the plugin is installed and your project is successfully configured, you can enjoy a more productive way of developing Java applications without wasting time on unnecessary waiting.
If you would like to learn more about JRebel, how to configure it for your projects, common pitfalls, or how it works under the hood, there's an instructive webinar you can check out: ZT Master Class: Intro to JRebel.