Выполнение приложения Java в отдельном процессе - PullRequest
42 голосов
/ 12 марта 2009

Может ли приложение Java быть загружено в отдельный процесс, используя его имя, в отличие от его расположения, независимо от платформы?

Я знаю, что вы можете запустить программу через ...

Process process = Runtime.getRuntime().exec( COMMAND );

... основная проблема этого метода заключается в том, что такие вызовы зависят от платформы.


В идеале я бы обернул метод во что-то такое простое, как ...

EXECUTE.application( CLASS_TO_BE_EXECUTED );

... и передайте полное имя класса приложения как CLASS_TO_BE_EXECUTED.

Ответы [ 9 ]

64 голосов
/ 07 апреля 2009

Это обобщение некоторых других ответов, которые были предоставлены. Системные свойства Java предоставляют достаточно информации, чтобы найти путь к команде java и пути к классам, что, как мне кажется, не зависит от платформы.

public final class JavaProcess {

    private JavaProcess() {}        

    public static int exec(Class klass) throws IOException,
                                               InterruptedException {
        String javaHome = System.getProperty("java.home");
        String javaBin = javaHome +
                File.separator + "bin" +
                File.separator + "java";
        String classpath = System.getProperty("java.class.path");
        String className = klass.getName();

        ProcessBuilder builder = new ProcessBuilder(
                javaBin, "-cp", classpath, className);

        Process process = builder.inheritIO().start();
        process.waitFor();
        return process.exitValue();
    }

}

Вы бы запустили этот метод так:

int status = JavaProcess.exec(MyClass.class);

Я подумал, что имеет смысл передать реальный класс, а не строковое представление имени, так как класс должен все равно находиться в пути к классам, чтобы это работало.

41 голосов
/ 12 марта 2009

Два совета:

System.getProperty("java.home") + "/bin/java" дает вам путь к исполняемому файлу Java.

((URLClassLoader) Thread.currentThread().getContextClassLoader()).getURL() помогает восстановить путь к классу текущего приложения.

Тогда ваш EXECUTE.application просто (псевдокод):

Process.exec(javaExecutable, "-classpath", urls.join(":"), CLASS_TO_BE_EXECUTED)

5 голосов
/ 08 февраля 2016

Если продолжить ответ @ stepancheg, реальный код будет выглядеть так (в форме теста).

import org.junit.Test;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.stream.Collectors;

public class SpinningUpAJvmTest {
    @Test
    public void shouldRunAJvm() throws Exception {
        String classpath = Arrays.stream(((URLClassLoader) Thread.currentThread().getContextClassLoader()).getURLs())
                .map(URL::getFile)
                .collect(Collectors.joining(File.pathSeparator));
        Process process = new ProcessBuilder(
                System.getProperty("java.home") + "/bin/java",
                "-classpath",
                classpath,
                MyMainClass.class.getName()
                // main class arguments go here
        )
                .inheritIO()
                .start();
        int exitCode = process.waitFor();
        System.out.println("process stopped with exitCode " + exitCode);
    }
}
4 голосов
/ 12 апреля 2009

Это может быть излишним для вас, но Project Akuma делает то, что вы хотите, и даже больше. Я нашел это через эту запись в невероятно полезном блоге Kohsuke (одного из программистов Sun Rock).

3 голосов
/ 07 апреля 2009

Вы проверяли API-интерфейс ProcessBuilder? Это доступно с 1.5

http://java.sun.com/javase/6/docs/api/java/lang/ProcessBuilder.html

3 голосов
/ 05 апреля 2009
public abstract class EXECUTE {

    private EXECUTE() { /* Procedural Abstract */ }

    public static Process application( final String CLASS_TO_BE_EXECUTED ) {

        final String EXEC_ARGUMENT 
        = new StringBuilder().
              append( java.lang.System.getProperty( "java.home" ) ).
              append( java.io.File.separator ).
              append( "bin" ).
              append( java.io.File.separator ).
              append( "java" ).
              append( " " ).
              append( new java.io.File( "." ).getAbsolutePath() ).
              append( java.io.File.separator ).
              append( CLASS_TO_BE_EXECUTED ).
              toString();

        try {       

            return Runtime.getRuntime().exec( EXEC_ARGUMENT );

        } catch ( final Exception EXCEPTION ) {     

            System.err.println( EXCEPTION.getStackTrace() );
        }

        return null;
    }
}
3 голосов
/ 12 марта 2009

Вы действительно должны запускать их изначально? Не могли бы вы просто вызвать их "основные" методы напрямую? Единственная особенность main в том, что средство запуска VM вызывает ее, ничто не мешает вам вызывать main самостоятельно.

1 голос
/ 03 ноября 2009

Проблема, которая возникает при запуске из графического интерфейса Java, это работает в фоновом режиме. Таким образом, вы вообще не видите командную строку.

Чтобы обойти это, вам нужно запустить java.exe через «cmd.exe» И «start». Я не знаю почему, но если вы вставите в начало команду «cmd / c start», она покажет командную строку при запуске.

Однако проблема с «запуском» заключается в том, что если в пути к приложению есть пробел (который путь к Java Exe обычно имеет, как это в C: \ Program Files \ Java \ jre6 \ bin \ java.exe или аналогичные), затем запуск просто не удается с "не могу найти c: \ Program"

Таким образом, вы должны поместить кавычки в C: \ Program Files \ Java \ jre6 \ bin \ java.exe Теперь начать жаловаться на параметры, которые вы передаете java.exe: "Система не может найти файл -cp."

Выход из пробела в «Program Files» с обратной косой чертой также не работает. Таким образом, идея состоит в том, чтобы не использовать пространство. Создайте временный файл с расширением bat, а затем поместите в него команду с пробелами и запустить летучую мышь. Однако запуск летучей мыши через старт не завершается, поэтому вы должны поставить «выход» в конце командного файла.

Это все еще кажется отвратительным.

Итак, в поисках альтернатив я обнаружил, что использование кавычек в кавычках в пространстве «Program Files» фактически работает с запуском.

В приведенном выше классе EXECUTE изменение строителя строки добавляет:

append( "cmd /C start \"Some title\" " ).
append( java.lang.System.getProperty( "java.home" ).replaceAll(" ", "\" \"") ).
append( java.io.File.separator ).
append( "bin" ).
append( java.io.File.separator ).
append( "java" ).
append( " " ).
append( new java.io.File( "." ).getAbsolutePath() ).
append( java.io.File.separator ).
append( CLASS_TO_BE_EXECUTED ).
1 голос
/ 06 апреля 2009

Следуя сказанному TofuBeer: вы уверены, что вам действительно нужно отключить другую JVM? В настоящее время JVM действительно хорошо поддерживает параллелизм, поэтому вы можете получить множество функциональных возможностей за сравнительно дешевую цену, просто раскрутив новый поток или два (для этого может потребоваться или не потребоваться вызов Foo # main (String [])). Проверьте java.util.concurrent для получения дополнительной информации.

Если вы решили раскошелиться, вы настроили себя на небольшую сложность, связанную с поиском необходимых ресурсов. То есть, если ваше приложение часто меняется и зависит от нескольких jar-файлов, вам необходимо отслеживать их все, чтобы они могли быть переданы в аргумент classpath. Кроме того, при таком подходе требуется вывести как местоположение (в настоящее время выполняющееся) JVM (которое может быть неточным), так и местоположение текущего пути к классам (которое даже менее вероятно будет точным, в зависимости от способа, которым нерест Была вызвана нить - jar, jnlp, взорванный .classes dir, некоторый контейнер и т. Д.).

С другой стороны, ссылки на статические методы #main также имеют свои подводные камни. Статические модификаторы имеют неприятную тенденцию просачиваться в другой код и обычно недовольны разработчиками.

...