tags:

views:

179

answers:

4

I would like to spawn a subprocess Java Virtual Machine (through a Java API call) and communicate with it.

Maybe I'm just not searching for the right thing on Google, but I keep getting pointed back to Runtime.exec(), which is not what I want. I know I could connect standard input/output and use various serialization techniques, or use remote method invocation, but these seem to cumbersome/heavyweight. It seems like an API call could be more robust than attempting to call Runtime.exec("/usr/bin/java -cp [system.getclasspath] ...").

The purpose / motivation of this is that dynamically reloading classes with many dependencies seems somewhat tricky (several sites mention reading a .class file and passing it to [ClassLoader].defineClass, but this doesn't handle loading of dependent class files well). If I don't need much communication bandwidth or low latency, I think a new JVM instance would work fine; it's that communicating with it seems cumbersome. In any case this question isn't high priority; I'll probably go the straightforward route and see how I can load dependent class files (e.g. ones for inner classes).

+2  A: 

As long as the classes you want to load are in a JAR file or a directory tree separate from your main app, using an URLClassLoader and running them in a separate Thread works fine. AFAIK all Java app servers work like this, so it's definitely a proven technique.

Michael Borgwardt
Unfortunately, I do want a new JVM instance; as (perhaps unclearly) stated in the "motivation" paragraph, I essentially have some different .class files of the same fully qualified class name (auto generated, so probably difficult to change). As at http://java.sun.com/j2se/1.4.2/docs/api/java/lang/ClassLoader.html#loadClass%28java.lang.String,%20boolean%29 , loadClass will first search for already loaded classes (findLoadedClass), so I can't simply use a URL loader on the new classes.
gatoatigrado
That's not a reason to start a new VM. It's definitely possible to have two different versions of a class with the same fully qualified name in the same VM, exactly because it's considered a *different* class when loaded with a different ClassLoader.
Michael Borgwardt
A class is a unique combination of its name and its class loader. Application servers can have many versions of the same class name and have some reloaded with effecting others. Valid uses for start a new JVM include; changing the GC options, running a different version of Java.
Peter Lawrey
Yes, thanks to both of you for the good answers, I did find that JVM was actually calling the loadClass method on the class loader, so all I had to do was make it load any submodules.Code here for anyone who wants to copy + paste: http://sites.google.com/a/gatoatigrado.com/gatoatigrado/code-snippets
gatoatigrado
+2  A: 

I would definitely recommend taking a look at the class loader mechanism used by the Tomcat servlet container - they seem to have exactly the same class loading problem and seem to have solved it very well.

Jared
A: 

If you go the communication route you should consider Java RMI.

Pool
A: 

ClassRunner (shown in the listing below) uses ProcessBuilder to run the main function of any available class in your classpath using the same class path and library path as the current JVM. It will even clone the environment and the working directory of the current JVM.

Caveat: ClassRunner assumes that java is on the PATH of the current JVM. You may want to put some logic around locating java or java.exe based on System.getProperty("java.home") :)

Listing of ClassRunner.java:

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class ClassRunner
{
    private final Class<?> classToRun;

    public ClassRunner(Class<?> classToRun)
    {
        this.classToRun = classToRun;
    }

    public void run(String... args) throws Exception
    {
        String javaCommand = "java";
        String libraryPath = "-Djava.library.path=\"" + System.getProperty("java.library.path") + "\"";
        String classpath = "\"" + System.getProperty("java.class.path") + "\"";
        ProcessBuilder processBuilder = new ProcessBuilder(javaCommand,
                libraryPath,
                "-classpath", classpath,
                classToRun.getCanonicalName());
        processBuilder.redirectErrorStream();

        for (String arg : args) processBuilder.command().add(arg);
        Process process = processBuilder.start();
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line;
        while ((line = reader.readLine()) != null) System.out.println(line);
        reader.close();
        process.waitFor();
    }

    public static void main(String[] args) throws Exception
    {
        new ClassRunner(Main.class).run("Hello");
    }
}

Listing of Main.java:

public class Main
{
    public static void main(String... args)
    {
        System.out.println("testing Main");
        for (String arg : args) System.out.println(arg);
    }
}
Alain O'Dea
The reason I believe this is a valid approach is because Threads don't save you when you need an isolated global state like HTTPS doing SSL without certificate validation.
Alain O'Dea
Akka's Typed Actors and Remote Actors http://doc.akkasource.org/typed-actors-java are a much richer solution to your whole problem. I wish they had detailed Java examples for running Remote Actors http://doc.akkasource.org/remote-actors-scala.
Alain O'Dea