Как запустить полностью независимый процесс из Java-программы?
Я работаю над программой, написанной на Java, которая для некоторых действий запускает внешние программы, используя настраиваемые пользователем командные строки. В настоящее время он использует Runtime.exec()
и не сохраняет ссылку Process
(запущенные программы являются либо текстовым редактором, либо архивной утилитой, поэтому нет необходимости в потоках системы /out/err ).
Есть небольшая проблема с этим, хотя в том, что когда программа Java выходит, она не прекращается, пока все запущенные программы не будут завершены.
Я бы очень хотел, чтобы запущенные программы были полностью независимы от JVM, которые их запускали.
Целевая операционная система является множественной, поскольку Windows, Linux и Mac являются минимальными, но любая система GUI с JVM действительно является желаемой (отсюда и пользовательская конфигурация реальных команд).
Кто-нибудь знает, как запустить запущенную программу полностью независимо от JVM?
Изменить в ответ на комментарий
Код запуска выглядит следующим образом. Код может запускать редактор, расположенный в определенной строке и столбце, или может запускать средство просмотра архива. Цитируемые значения в сконфигурированной командной строке трактуются как кодированные ECMA-262 и декодируются, а кавычки разделяются, чтобы сформировать требуемый параметр exec.
Запуск происходит на EDT.
static Throwable launch(String cmd, File fil, int lin, int col) throws Throwable {
String frs[][]={
{ "$FILE$" ,fil.getAbsolutePath().replace('\\','/') },
{ "$LINE$" ,(lin>0 ? Integer.toString(lin) : "") },
{ "$COLUMN$",(col>0 ? Integer.toString(col) : "") },
};
String[] arr; // array of parsed tokens (exec(cmd) does not handle quoted values)
cmd=TextUtil.replace(cmd,frs,true,"$$","$");
arr=(String[])ArrayUtil.removeNulls(TextUtil.stringComponents(cmd,' ',-1,true,true,true));
for(int xa=0; xa<arr.length; xa++) {
if(TextUtil.isQuoted(arr[xa],true)) {
arr[xa]=TextDecode.ecma262(TextUtil.stripQuotes(arr[xa]));
}
}
log.println("Launching: "+cmd);
Runtime.getRuntime().exec(arr);
return null;
}
Это, похоже, происходит только тогда, когда программа запускается из моей среды IDE. Я закрываю этот вопрос, поскольку проблема существует только в моей среде разработки; это не проблема в производстве. Из тестовой программы в одном из ответов и последующем тестировании, которое я провел, я удовлетворен тем, что это не проблема, которую может наблюдать любой пользователь программы на любой платформе.
Ответы
Ответ 1
Это может помочь, если вы разместите тестовый раздел минимального кода, необходимый для воспроизведения проблемы. Я протестировал следующий код в Windows и Linux.
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws Exception {
Runtime.getRuntime().exec(args[0]);
}
}
И протестирован на Linux:
java -jar JustForTesting.jar /home/monceaux/Desktop/__TMP/test.sh
где test.sh выглядит так:
#!/bin/bash
ping -i 20 localhost
а также в Linux:
java -jar JustForTesting.jar gedit
И протестировал это в Windows:
java -jar JustForTesting.jar notepad.exe
Все они запустили свои намеченные программы, но у приложения Java не было проблем с выходом. У меня есть следующие версии Sun JVM, о которых сообщает java -version
:
- Windows: 1.6.0_13-b03
- Linux: 1.6.0_10-b33
У меня еще не было возможности протестировать мой Mac. Возможно, в вашем проекте есть какое-то взаимодействие с другим кодом, который может быть неясным. Вы можете попробовать это тестовое приложение и посмотреть, что результаты.
Ответ 2
Между вашими процессами существует родительское дочернее отношение, и вам нужно это сломать.
Для Windows вы можете попробовать:
Runtime.getRuntime().exec("cmd /c start editor.exe");
Для Linux процесс, похоже, все равно отключается, nohup необходимо.
Я пробовал его с помощью gvim
, midori
и acroread
.
import java.io.IOException;
public class Exec {
public static void main(String[] args) {
try {
Runtime.getRuntime().exec("/usr/bin/acroread");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Finished");
}
}
Я думаю, что это невозможно сделать с Runtime.exec независимым от платформы способом.
для POSIX-совместимой системы:
Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", "your command"}).waitFor();
Ответ 3
Хотя этот вопрос закрыт, у меня есть некоторые замечания, которые могут помочь другим людям, столкнувшимся с подобной проблемой.
Когда вы используете Runtime.getRuntime(). exec(), а затем игнорируете дескриптор java.lang.Process, который вы возвращаете (например, в коде с оригинального плаката), есть вероятность, что запущенный процесс может зависать.
Я столкнулся с этой проблемой в среде Windows и проследил проблему с потоками stdout и stderr. Если запущенное приложение записывает эти потоки, и буфер для этого потока заполняется, тогда запущенное приложение может оказаться зависающим, когда оно пытается выполнить запись в потоки. Решения:
- Захват дескриптора процесса и периодическое удаление потоков - но если вы хотите прервать Java-приложение сразу после запуска процесса, это не выполнимое решение.
- Выполните вызов процесса как 'cmd/c < > ' (это только для среды Windows).
- Суффикс команды процесса и перенаправление потоков stdout и stderr в nul с помощью команды "nul 2 > & 1"
Ответ 4
Вы хотите запустить программу в фоновом режиме и отделить ее от родителя. Я бы рассмотрел nohup (1).
Ответ 5
Я подозреваю, что для этого потребуется фактическая вилка процесса. В принципе, C эквивалент того, что вы хотите:
pid_t id = fork();
if(id == 0)
system(command_line);
Проблема в том, что вы не можете сделать fork() в чистой Java. Я бы сделал следующее:
Thread t = new Thread(new Runnable()
{
public void run()
{
try
{
Runtime.getRuntime().exec(command);
}
catch(IOException e)
{
// Handle error.
e.printStackTrace();
}
}
});
t.start();
Таким образом, JVM все равно не выйдет, но графический интерфейс и ограниченный объем памяти не останется.
Ответ 6
Один из способов, о котором я могу думать, - использовать Runtime.addShutdownHook для регистрации потока, который уничтожает все процессы (вам нужно было бы сохранить объекты процесса где-то, конечно).
Крюк выключения вызывается только при выходе из JVM, поэтому он должен работать нормально.
Немного взлома, но эффективный.