ssledz blog

Everything should be made as simple as possible, but no simpler.

Hot Swap in Java With DCEVM and HotSwapAgent - a JRebel Free Alternative

Reloading a bytecode in a virtual machine when application is running is very limited. In fact HotSpot(TM) VM allows only changing method bodies. To address this problem some commercial and open source tools were created. Among them is Dynamic Code Evolution Virtual Machine (DCEVM) and HotSwapAgent - very promising open source tool.

I have already some experience in using DCEVM. Some times ago I have been working for an insurance company where I was using this modified vm to develop a code in a gosu language. Gosu is another JVM language. I remember that then hot swapping worked very well.

Let’s try this tool. First we need to patch our current jvm.

Installing Dynamic Code Evolution VM

In order to enhance current Java (JRE/JDK) installations with DCEVM you need to download the latest release of DCEVM installer for a given major java version,

java 7 and java 8 are supported

run the installer

1
java -jar DCEVM-light-8u74-installer.jar

then select a proper java installation directory on your disc and press Install DCEVM as altjvm

That’s all really. Very simple isn’t it ?

To validate the installation run:

1
java -version

and if everything went alright you should see something similar to below output:

1
2
3
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Dynamic Code Evolution 64-Bit Server VM (build 25.71-b01-dcevmlight-10, mixed mode)

Note that in the third line instead of Java HotSpot(TM) we have now Dynamic Code Evolution.

Kind of installers

Worth noting is the fact that there are two kind of installers

  • light
  • and full

The latter one supports more features (for example, it supports removal of superclasses), but because of the maintenance issues the full edition is available for a fewer versions of jdk.

Downloading HotswapAgent

HotswapAgent does the work of reloading resources and framework configuration. So in order to have a support for reloading a spring bean definitions just after a change occurs, we need to perform one more step - download latest release of hotswap-agent.jar and put it anywhere. For example here: ~/bin/hotswap/hotswap-agent.jar.

Running application in order to test hot swapping

I will use Main and Main2 classes to play with hot swapping:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Main {

    static class Foo {
        int counter;

        void foo() {
            System.out.printf("foo - %08d", counter);
            counter++;
        }
    }

    static Foo foo = new Foo();

    static int counter = 0;

    static void mainLoop() {
        System.out.printf("tick - %08d\t", counter++);
        foo.foo();
        System.out.println();
    }

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            mainLoop();
            Thread.sleep(2000);
        }
    }
}

And the second one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main2 {

    static class Foo {

    }

    static void mainLoop() {

        String fields = Stream.of(Foo.class.getFields())
                .map(f -> f.getName())
                .collect(Collectors.joining(",", "[", "]"));
        String methods = Stream.of(Foo.class.getDeclaredMethods())
                .map(m -> m.getName())
                .collect(Collectors.joining(",", "[", "]"));

        System.out.printf("fields=%s\t methods=%s\n", fields, methods);

    }

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            mainLoop();
            Thread.sleep(2000);
        }
    }
}

I will test following use cases:

  case works ? Test class
1. change body method YES Main
2. add method YES Main
3. add field YES Main
4. remove field YES Main2
5. remove method YES Main2

Intellij IDEA settings

All tests will be performed using Intellij IDEA. Ensure that following options are set

  • pass -XXaltjvm=dcevm vm option to run/debug configuration

Case 1 : change body method

Run debug. In the console you should see following output:

1
2
3
4
tick - 00000000	foo - 00000000
tick - 00000001	foo - 00000001
tick - 00000002	foo - 00000002
tick - 00000003	foo - 00000003

Then change counter++; to counter+=2; in Foo class.

1
2
3
4
5
6
7
8
static class Foo {
    int counter;

    void foo() {
        System.out.printf("foo - %08d", counter);
        counter+=2;
    }
}

Hit <ctr>+<shift>+<F9> to compile and after few seconds you should spot that the classes were reloaded successfully

1
2
3
tick - 00000004	foo - 00000004
tick - 00000005	foo - 00000006
tick - 00000006	foo - 00000008

Case 2 : add method

Revert all changes in Main class and run debug. Add method

1
2
3
void bar() {
    System.out.printf("\tbar - %08d", counter);
}

to the Foo class and call it from the mainLoop

1
2
3
4
5
6
static void mainLoop() {
    System.out.printf("tick - %08d\t", counter++);
    foo.foo();
    foo.bar();
    System.out.println();
}

Hit <ctr>+<shift>+<F9> to compile.

1
2
3
4
5
tick - 00000003	foo - 00000003
tick - 00000004	foo - 00000004
tick - 00000005	foo - 00000005	bar - 00000006
tick - 00000006	foo - 00000006	bar - 00000007
tick - 00000007	foo - 00000007	bar - 00000008

Classes were reloaded successfully.

Case 3 : add field

Revert all changes in Main class and run debug. Add field int counter2 to Foo class and append following two statements to the end of foo method.

1
2
System.out.printf("\tcounter2 - %08d", counter2);
counter2++;

Foo class should look following

1
2
3
4
5
6
7
8
9
10
11
12
static class Foo {
    int counter;

    int counter2;

    void foo() {
        System.out.printf("foo - %08d", counter);
        counter++;
        System.out.printf("\tcounter2 - %08d", counter2);
        counter2++;
    }
}

Hit <ctr>+<shift>+<F9> to compile. And appears

1
2
3
4
5
tick - 00000002	foo - 00000002
tick - 00000003	foo - 00000003
tick - 00000004	foo - 00000004	counter2 - 00000000
tick - 00000005	foo - 00000005	counter2 - 00000001
tick - 00000006	foo - 00000006	counter2 - 00000002

that this kind of change was also reloaded successfully.

Case 4 : remove field

Run debug. In the console you should see following output:

1
2
3
fields=[]	 methods=[]
fields=[]	 methods=[]
fields=[]	 methods=[]

Then add two public fields int number and String name to the Foo class.

1
2
3
4
static class Foo {
    public int number;
    public String name;
}

Hit <ctr>+<shift>+<F9> to compile, and after few seconds…

1
2
3
fields=[]	 methods=[]
fields=[number,name]	 methods=[]
fields=[number,name]	 methods=[]

Then remove number field and hit again <ctr>+<shift>+<F9>.

1
2
3
fields=[number,name]	 methods=[]
fields=[name]	 methods=[]
fields=[name]	 methods=[]

The change was reloaded.

Case 5 : remove method

Revert all changes in Main2 class and run debug. Add

  • public field String name
  • public method String getName()

to the Foo class. Then hit <ctr>+<shift>+<F9> to compile, and after few seconds…

1
2
3
fields=[]	 methods=[]
fields=[name]	 methods=[getName]
fields=[name]	 methods=[getName]

Then remove getName() method and hit again <ctr>+<shift>+<F9>.

1
2
3
fields=[name]	 methods=[getName]
fields=[name]	 methods=[]
fields=[name]	 methods=[]

Seems that this change was also reloaded successfully.

Notes

During the play with hot swapping in Intellij IDEA you could notice that for some circumstances code would not be reloaded. Intellij IDEA has a following limitation about which you need to be aware:

the old code is still used until the VM exits the obsolete stack frame

About that you can read here

Resources

Comments