James Moore
James Moore

Reputation: 3617

Updating a JAR whilst running

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?

Upvotes: 37

Views: 25259

Answers (9)

Alexander Solovets
Alexander Solovets

Reputation: 2507

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.

Upvotes: 0

javadev
javadev

Reputation: 790

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

Upvotes: 1

VocoJax
VocoJax

Reputation: 1549

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!

Upvotes: 0

Piyush Katariya
Piyush Katariya

Reputation: 765

You can use HotswapAgent to achieve it. It supports plugins for a few widely used frameworks and also facilitates writing new custom plugins

HotswapAgent - https://github.com/HotswapProjects/HotswapAgent

Upvotes: 0

toomasr
toomasr

Reputation: 4781

This is something that I've seen done many times before (and also done myself). I composed some points of the problems/solutions that might arise.

  • JVM will crash with a dump if you overwrite a JAR file that it will use later on.
    • By later I mean classes are loaded quite lazily and some might only be loaded later in your program's life
    • JVM has an open handle for the JAR file and the lib will fail as the JAR and pointers become wrong
    • The probability can be decreased by pre-loading all classes and resources from the JAR file
    • If you have a custom classloader then you can close the handles yourself.
  • You will need to know about how class loading is done. Better yet be in control.
    • A custom classloader that will create a classloaders per JAR and manage the versioning
    • Know how your application uses classloaders and how it acts on new JARs (for example check what Tomcat does when you overwrite a WAR archive)
  • On Windows your JAR files will be locked and you cannot overwrite them. If you are in control then you can unlock them after use (close them). For 3rd party systems you have to find the respective flags. For example you can check the antiJARLocking in Tomcat context configuration.
  • Always better to avoid overwriting the same file and rather have some versioning going on

All in all there are many issues that you might run into when you want to achieve JAR reloading. Luckily there are ways how to minimise the risks. The safest way is to do something similar to get the same effect. Cleanest is custom classloaders and JAR file versioning.

Upvotes: 30

Perception
Perception

Reputation: 80603

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 update a Jar whilst its running?

The JAR file is not 'running', the JVM is running. Your JAR file just contains the class information (aka byte code instructions) that make the JVM do useful work. In most cases the JVM will actually not put a system lock on your JAR file, so you can replace that file to your hearts content.

The real problem of course is that once the JVM loads your JAR it will carry along happily with what it loaded and never read from your JAR file again, no matter how many times you overwrite it. This is the behavior of the default class loader and cannot be changed - however as others have pointed out - you do NOT have to use the default class loader. You can implement your own, similar to what Web Application Servers use, in order to load updated JARS from the filesystem. Caveat though - defining your own classloader is considered a 'Bad Idea™' unless you really know what your doing. You can read more here and here.

Upvotes: 31

Nick
Nick

Reputation: 5805

The answer lies in Java Class Loaders. These guys load classes from JARS, or .class files, or a byte[] value or a URL or anything else. Whenever you access a class, you are implicitly using a class loader to give you the right instance of a class.

Create a class loader of your choice and just switch the class loader when you need a "refresh" of your classes. Take a look at the Thread.setContextClassLoader method -- this will change a Thread's classloader.

Defining your own Class Loader is very straighforward -- just subclass the ClassLoader class and override its findClass method.

Upvotes: 1

Snicolas
Snicolas

Reputation: 38168

You can't write to a jar that is running. There is no equivalent to getResourceInputStream for writing. I guess that if you try to write using an FileOutputStream, as JVM uses it, you won't be able to delete it as System will prevent it.

Anyhow, it's still possible to offer updates of different modules in different jars. So you could imagine having an application's main jar file that could be updated through a small independant runnable jar file containing the updater.

It's also possible to use JNLP for automatic and seamless updates of an application.

Server side applications are also an alternative to hide updates to user.

Regards, Stéphane

Upvotes: 1

Generally you cannot do this as this behaviour to my knowledge is not officially defined.

You CAN however create a classloader using a jar file outside your official classpath and then load classes from that as you need. By discarding all instances of classes loaded by the classloader you can remove the current resources and then instantiate a new classloader on the new jar file and then load the new classes and create new objects.

This is quite complicated so perhaps you would instead make the jar an OSGi module and invoke your program through an OSGi-loader?

Upvotes: 2

Related Questions