views:

472

answers:

5

I have a server written in Java that runs as a Windows service (thanks to Install4J). I want this service to be able to download the latest version of the JAR file it runs from, and start running the new code. The stitch is that I don't want the Windows service to fully exit.

Ideally, I would accomplish this by a unix-style exec() call to stop the current version and run the new one. How can I best accomplish this?

+1  A: 

As far as I know, there is no way to do this in Java.

I suppose you could work around it by using the Java Runtime.exec or ProcessBuilder's start() command (which start new processes) then letting the current one end... the docs state

The subprocess is not killed when there are no more references to the Process object, but rather the subprocess continues executing asynchronously.

I'm assuming the same is true if the parent finishes and is garbage collected.

The catch is Runtime.exec's process will no longer have valid in, out, and err streams.

R. Bemrose
A: 

General approach:

import java.io.BufferedInputStream;
import java.util.Arrays;

public class ProcessSpawner {    
public static void main(String[] args) {
    //You can change env variables and working directory, and
    //have better control over arguments.
    //See [ProcessBuilder javadocs][1]
    ProcessBuilder builder = new ProcessBuilder("ls", "-l");

    try {
        Process p = builder.start();
        //here we just echo stdout from the process to java's stdout.
        //of course, this might not be what you're after.
        BufferedInputStream stream = 
            new BufferedInputStream(p.getInputStream());
        byte[] b = new byte[80];
        while(stream.available() > 0) {                 
            stream.read(b);
            String s = new String(b);
            System.out.print(s);
            Arrays.fill(b, (byte)0);
        }
        //exit with the exit code of the spawned process.
        System.exit(p.waitFor());

    } catch(Exception e) {
        System.err.println("Exception: "+e.getMessage());
        System.exit(1);
    }
  }
}
gnud
+1  A: 

Here is a complicated, but portable, way.

Split your code into two jars. One very small jar is there just to manage process startup. It creates a ClassLoader that holds the other jar on its classpath.

When you want to load a new version, you terminate all threads running code from the old jar. Null out all references to instances of classes from the old jar. Null out all references to the ClassLoader that loaded the old jar. At this point, if you didn't miss anything, the old classes and ClassLoader should be eligible for garbage collection.

Now you start over with a new ClassLoader instance pointing at the new jar, and restart your application code.

Darron
A: 

You could use the built in reloading functionality of for example Tomcat, Jetty or JBoss, they can be run as a service, and you don't have to use them as a web container or JEE container.

Other options are OSGi, and your own class loading functionality.

But be aware of reloading in a production environment. It is not always the best solution.

Hans Doggen
+1  A: 

Java Service Wrapper does this and more.

ddimitrov