Как запустить полностью независимый процесс из Java-программы? - PullRequest
47 голосов
/ 31 мая 2009

Я работаю над программой, написанной на Java, которая для некоторых действий запускает внешние программы, используя настроенные пользователем командные строки. В настоящее время он использует Runtime.exec() и не сохраняет ссылку Process (запущенные программы являются либо текстовым редактором, либо утилитой архивирования, поэтому система не нуждается в потоках / out / err).

Однако существует небольшая проблема, заключающаяся в том, что при выходе из программы Java она не завершается, пока не будут завершены все запущенные программы.

Я бы предпочел, чтобы запущенные программы были полностью независимы от JVM, запустившей их.

Целевая операционная система является множественной, Windows, Linux и Mac являются минимальными, но любая система с графическим интерфейсом и 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. Я закрываю этот вопрос, поскольку проблема существует только в моей среде разработки; это не проблема на производстве . Из тестовой программы в одном из ответов и дальнейшего тестирования, которое я провел, я убежден, что это не проблема, которая будет видна любому пользователю программы на любой платформе.

Ответы [ 7 ]

28 голосов
/ 31 мая 2009

Между вашими процессами есть родительско-дочерние отношения, и вы должны их разорвать. Для 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();
21 голосов
/ 13 июня 2012

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

Когда вы используете Runtime.getRuntime (). Exec (), а затем игнорируете дескриптор java.lang.Process, который вы возвращаете (как в коде из исходного постера), есть вероятность, что запущенный процесс может зависнуть.

Я столкнулся с этой проблемой в среде Windows и отследил ее до потоков stdout и stderr. Если запущенное приложение выполняет запись в эти потоки, а буфер для этих потоков заполняется, то при попытке записи в потоки запущенное приложение может зависнуть. Решения:

  1. Захватите дескриптор процесса и непрерывно очищайте потоки, но если вы хотите завершить работу Java-приложения сразу после запуска процесса, это нереальное решение
  2. Выполнить вызов процесса как «cmd / c <>» (это только для среды Windows).
  3. Суффиксуйте команду процесса и перенаправьте потоки stdout и stderr в nul, используя ' command> nul 2> & 1 '
17 голосов
/ 31 мая 2009

Может помочь, если вы разместите тестовый раздел с минимальным кодом, необходимым для воспроизведения проблемы. Я протестировал следующий код в 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. Возможно, в вашем проекте происходит некоторое взаимодействие с другим кодом, который может быть неясным. Вы можете попробовать это тестовое приложение и посмотреть, каковы результаты.

1 голос
/ 31 мая 2009

Вы хотите запустить программу в фоновом режиме и отделить ее от родительской. Я бы рассмотрел nohup (1) .

0 голосов
/ 26 февраля 2019

Я перепробовал все упомянутое здесь, но безуспешно. Основной родительский Java-процесс не может завершить работу до тех пор, пока не завершится подпроцесс даже при запуске cmd / c и переадресации потоков.

Только одно надежное решение для меня это:

try {
    Runtime.getRuntime().exec("psexec -i cmd /c start cmd.cmd");
}
catch (Exception e) {
    // handle it
}

Я знаю, что это не ясно, но эта небольшая утилита от SysInternals очень полезна и проверена. Здесь - ссылка.

0 голосов
/ 31 мая 2009

Один из способов, который я могу придумать, - это использовать Runtime.addShutdownHook, чтобы зарегистрировать поток, который убивает все процессы (вам, конечно, нужно где-то сохранить объекты процесса).

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

Немного взломать, но эффективно.

0 голосов
/ 31 мая 2009

Я подозреваю, что это потребовало бы фактической обработки процесса. По сути, 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 все равно не выйдет, но графического интерфейса не останется, и останется только ограниченный объем памяти.

...