Updating a JAR whilst running

Viewed 22502

Given a jar runs within a JVM would it be possible to unload the current running Jar and remove it from the system. Download a new version and rename it with the same name of the last Jar and then initialise the new Jar, creating a seamless update of the Jar within the JVM. Is it even possible to instruct the JVM to perform this action? Is it even possible to update a Jar whilst its running?

9 Answers

Please pay attention to Tom Hubbard's comment. If a class was not used yet in Runtime (and therefore not loaded), if you remove the class in the new JAR - you will have some issues.

For example, if you will execute the following code:

    public static void main(String[] args) throws InterruptedException {
        long startTime = System.currentTimeMillis();
        while (true) {
            long now = System.currentTimeMillis();
            long delta = (now - startTime);
            System.out.println("Running... delta is " + delta);
            if (TimeUnit.MILLISECONDS.toSeconds(delta) > 30) {
                Me1 m1 = new M1();
                me1.method1(); // ###
            }
            Thread.sleep(1000);
        }
    }

If you put the above in a Jar and execute it, and before 30 seconds will pass, you will change the Jar such that the class "Me1" will not contain "method1" at all (and of course you will remove the line marked with "###"), after 30 seconds of execution you will get an exception:

    Exception in thread "main" java.lang.NoClassDefFoundError: Me1
    at Me.main(Me.java:16)
    Caused by: java.lang.ClassNotFoundException: Me1
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 1 more

In my software suite, which is a complex mesh of modular clients linked to a central server taking long-term timelapse photography, I have added the ability to update the software.

byte[] file = recieve();

FileOutputStream fos = new FileOutputStream("software.jar");
fos.flush(); fos.write(file); fos.close();

After this process is completed, there are a series of steps the client takes before it reboots. Of these processes, they include long periods of thread sleeping, file read and writing, and networking interactions.

I have not pinpointed what may or may not be the error, however, in some cases, I have observed a crash with an hs_err_pid.log.

I also have a variable, final and static, called "SOFTWARE_VERSION". I have confirmed that this variable does update (when observing from the server interface) without a reboot of the software after I replace it.

After some careful consideration, however, I've decided to immediately reboot the machine after a software update (this program will execute on startup). Since the integrity of an update has been observed to not be reliable, I found it best to give it a fresh start. It's possible (not tested) to run an action like this:

Runtime.getRuntime().exec("sudo java -jar software.jar");
System.exit(0);

However, I don't know how reliable that would be. You could also try to run something like:

Runtime.getRuntime().exec("run_software.sh")
System.exit(0);

Then in run_software.sh:

sleep 1000
sudo java -jar software.jar

I would be interested to know if that would work.

Hope this helps!

If JVM restart is allowed

As people mentioned, the JVM process will have the file handle of the jar file open until the end of the process life. And if Linux is your target OS then you can do the following trick.

  1. Delete the original JAR file using unlink(2) or equivalent method. The path will be erased from the filesystem metadata immediately, but the physical file data will be kept until the last file handle is closed, so no corruption will happen.
  2. Write new file to the same path. Even though the path is the same, the actual data will go to another block, so again - no data corruption, everything is 100% legal.
  3. Restart the JVM process using the same command-line as was used to start the original process.

If JVM restart is not allowed

Then the only way to update a class is - even with an re-defined class loader - to force the JVM to unload the class first. And the problem here is that JVM will only do that when garbage collecting the class loader. With standard implementations there is little to no control over that process.

Related