【问题标题】:How do I launch a completely independent process from a Java program?如何从 Java 程序启动完全独立的进程?
【发布时间】:2010-10-30 05:07:21
【问题描述】:

我正在开发一个用 Java 编写的程序,对于某些操作,它使用用户配置的命令行启动外部程序。目前它使用Runtime.exec() 并且不保留Process 引用(启动的程序是文本编辑器或存档实用程序,因此不需要系统输入/输出/错误流)。

这有一个小问题,当 Java 程序退出时,它并没有真正退出,直到所有启动的程序都退出。

如果启动的程序完全独立于启动它们的 JVM,我会非常喜欢它。

目标操作系统有多种,Windows、Linux 和 Mac 是最少的,但任何带有 JVM 的 GUI 系统都是真正需要的(因此实际命令行的用户可配置性)。

有谁知道如何让启动的程序完全独立于JVM执行?


根据评论进行编辑

启动代码如下。代码可能会启动位于特定行和列的编辑器,也可能会启动存档查看器。配置的命令行中引用的值被视为 ECMA-262 编码,并被解码并剥离引号以形成所需的 exec 参数。

发射发生在美国东部时间。

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 启动程序时才会发生。我要结束这个问题,因为这个问题只存在于我的开发环境中; 在生产中不是问题。从答案之一中的测试程序,以及我进行的进一步测试,我很满意这不是任何平台上程序的任何用户都会看到的问题。

【问题讨论】:

  • 我想你的意思是你目前正在使用 Runtime.exec()
  • @Chadwick:哎呀——是的,谢谢。
  • 我怀疑这只是一个 Windows 问题。当然,在 Mac 上,我遇到了相反的问题:确保我启动的外部进程确实在 JVM 退出时退出。

标签: java exec external-process


【解决方案1】:

您的进程之间存在父子关系,您必须打破它。 对于 Windows,您可以尝试:

Runtime.getRuntime().exec("cmd /c start editor.exe");

对于 Linux,该进程似乎是独立运行的,不需要 nohup。 我用gvimmidoriacroread 尝试过。

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();

【讨论】:

  • 我希望在进程创建中可以做一些事情......这需要用户配置“正确”的命令行。但很高兴知道。
  • 您可以让用户配置正确的命令行,并在必要时偷偷添加操作系统特定的魔法。
  • catch (IOException e) 既不替换 .waitFor() 也不替换 InterruptedException
  • 非常感谢。我的团队一直在调查我们从 java 启动的组件 C++ 软件的问题。事实证明,这是 JVM 进程强加的安全问题。这解决了这个问题。非常感谢。
  • ss64.com/nt/cmd.html 提供了有关如何将参数传递给“cmd”的附加信息。 ss64.com/nt/start.html 与“开始”相同。
【解决方案2】:

我有一些观察结果可能会对面临类似问题的其他人有所帮助。

当您使用 Runtime.getRuntime().exec() 然后忽略返回的 java.lang.Process 句柄(如原始海报中的代码),启动的进程可能会挂起。

我在 Windows 环境中遇到过这个问题,并将问题追溯到 stdout 和 stderr 流。如果启动的应用程序正在写入这些流,并且这些流的缓冲区已满,则启动的应用程序在尝试写入流时可能会出现挂起。解决方案是:

  1. 捕获进程句柄并不断清空流 - 但如果您想在启动进程后立即终止 Java 应用程序,那么这不是一个可行的解决方案
  2. cmd /c &lt;&lt;process&gt;&gt; 执行进程调用(仅适用于Windows 环境)。
  3. 使用 'command > nul 2>&1'
  4. 为进程命令添加后缀并将 stdout 和 stderr 流重定向到 nul

【讨论】:

  • 谢谢。在启动 kryonet 客户端的情况下完全为我工作。选项1可以使用ProcessBuilder的inheritIO方法来实现。它将所有流内容发送到父 Java 进程。所以不需要让进程独立
  • 我在编写自定义午餐程序时遇到问题,其中子进程(主要是铬和 pidgin)在运行几个小时后冻结,但午餐程序一退出,它们就会立即解冻。谷歌搜索和调试了一周,这是我发现的问题的最佳答案。如果这也是我的问题,我会给你 10 万分的答案。
  • ss64.com/nt/cmd.html 还提供了有关如何传入参数的更多信息。
  • 你救了我!!!我有两个罐子,一个启动另一个。如果子进程挂了,即使在父进程重新启动后,子进程仍然挂起。只有当我杀死父进程时,子进程才重新开始工作。使用 Runtime.getRuntime().exec(cmd /c java -jar myjar.jar args > nul 2>&1") 将 stdout 重定向到 null 修复它!!
  • 感谢 Manavendra 和 @Gaurav,它帮助我解决了过去 2 天一直在寻找的问题
【解决方案3】:

如果您发布重现问题所需的最少代码的测试部分可能会有所帮助。我在 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 应用程序退出时没有问题。根据java -version 的报告,我有以下版本的 Sun JVM:

  • Windows:1.6.0_13-b03
  • Linux:1.6.0_10-b33

我还没有机会在我的 Mac 上进行测试。也许在您的项目中与其他代码发生了一些可能不清楚的交互。您可能想试试这个测试应用,看看结果如何。

【讨论】:

  • 这个问题可能仅限于 Windows XP,或者可能只是测试期间从我的 IDE 启动 JVM 的方式造成的。在 Windows Vista 上,分布式构建不会发生这种情况。在 XP 上进行测试后,如果仅在从 IDE 启动 JVM 时显示问题,则此答案将被接受。
  • 这似乎只在从我的 IDE 启动时发生,所以我接受这个答案,表明我的问题只存在于我的开发环境中;这在生产中不是问题。
  • Monceaux:谢谢,顺便说一句,这个简单的测试让我更仔细地了解了程序没有退出的精确条件 - 我意识到这是我的 IDE 启动器,而不是未退出的 Java。
  • 我需要在完成主代码后继续运行进程,但是运行时进程看起来只在主类线程运行时运行,而当主线程终止时进程看起来被终止。因此,解决方法是在命令中生成 shell,例如 Windows 的 cmd /c start my.bat 和 Linux 的 xterm -e ./my.sh &。完成主程序后继续运行。
  • 请注意,这很危险。如果Runtime.getRuntime().exec(args[0]) 启动的程序产生的输出超出输出缓冲区的容量,应用程序将冻结。
【解决方案4】:

您想在后台启动程序,并将其与父程序分开。我会考虑nohup(1)

【讨论】:

  • 嗯,对于 Linux,当然……但问题不在于启动的进程正在终止,而是 JVM 不会在启动的程序结束之前结束也结束了。
  • 你真的尝试了吗?一个 nohup 的后台进程应该在 system() 调用中出现,就好像它立即终止了一样。
【解决方案5】:

我怀疑这需要一个实际的进程分叉。基本上,你想要的 C 等价物是:

pid_t id = fork();
if(id == 0)
  system(command_line);

问题是你不能在纯 Java 中执行 fork()。我会做的是:

Thread t = new Thread(new Runnable()
{
    public void run()
    {
      try
      {
          Runtime.getRuntime().exec(command);
      }
      catch(IOException e)
      {           
          // Handle error.
          e.printStackTrace();
      }
    }
});
t.start();

这样 JVM 仍然不会退出,但没有 GUI,只会留下有限的内存占用。

【讨论】:

  • 这正是 O.P 试图避免的问题。
【解决方案6】:

我尝试了这里提到的所有方法,但没有成功。即使使用 cmd /c start 和重定向流 tu nul,主父 Java 进程也无法退出,直到子线程退出。

对我来说只有一个可靠的解决方案是:

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

我知道这并不清楚,但是 SysInternals 的这个小实用程序非常有用且经过验证。 Here 是链接。

【讨论】:

    【解决方案7】:

    我能想到的一种方法是使用 Runtime.addShutdownHook 注册一个杀死所有进程的线程(当然,您需要在某个地方保留进程对象)。

    关闭挂钩仅在 JVM 退出时调用,因此它应该可以正常工作。

    有点小技巧但很有效。

    【讨论】:

    • 除非我不想杀死进程。我想要的是他们不要阻止 JVM 退出。我认为我的用户会在退出我的应用程序并看到其他几个程序随之消失时感到非常惊讶。
    猜你喜欢
    • 2018-05-12
    • 2012-11-15
    • 2015-02-21
    • 2014-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-12
    相关资源
    最近更新 更多