我如何从Java程序启动完全独立的进程?

我正在使用Java编写的程序, 对于某些操作 ,使用用户configuration的命令行启动外部程序。 目前它使用Runtime.exec()并不保留Process引用(启动的程序是文本编辑器或归档实用程序,因此不需要系统input/输出/ errstream)。

这个问题有一个小问题,那就是当Java程序退出的时候,直到所有已经启动的程序退出,它才真正退出。

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

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

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


编辑回应评论

启动代码如下。 代码可以启动位于特定行和列的编辑器,也可以启动存档查看器。 在configuration的命令行中引用的值被视为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启动程序时。 由于问题只存在于我的开发环境中,所以我正在closures这个问题。 这不是生产中的问题 。 从testing程序中的其中一个答案中,我进行了进一步的testing,我很满意这个程序的任何用户都不会在任何平台上看到这个问题。

如果您发布重现问题所需的最小代码的testing部分,可能会有所帮助。 我在Windows和Linux系统上testing了以下代码。

 public class Main { /** * @param args the command line arguments */ public static void main(String[] args) throws Exception { Runtime.getRuntime().exec(args[0]); } } 

并在Linux上进行了以下testing:

 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上testing:

 java -jar JustForTesting.jar notepad.exe 

所有这些都启动了他们预期的程序,但Java应用程序没有问题退出。 我有java -version报告的Sun的JVM的以下版本:

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

我还没有机会在我的Mac上进行testing。 也许有一些与您的项目中的其他代码交互,可能不清楚。 你可能想尝试这个testing应用程序,看看结果是什么。

在你的过程之间有一个父子关系,你必须打破这个关系。 对于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(); 

虽然这个问题是封闭的,但我也有一些观点可以帮助其他人面对类似的问题。

当你使用Runtime.getRuntime().exec(),然后你忽略你返回的java.lang.Process句柄(就像在原始的海报中的代码一样),那么有可能被启动的进程挂起。

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

  1. 捕获进程句柄并连续清空stream – 但是如果你想在启动进程后立即终止Java应用程序,那么这不是一个可行的解决scheme
  2. 作为'cmd / c <>'执行进程调用(这仅适用于Windows环境)。
  3. 后缀进程命令并将stdout和stderrstreamredirect到nul使用' command> nul 2>&1 '

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

我怀疑这将需要一个实际的stream程叉。 基本上,你想要的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,只剩下有限的内存空间。

我能想到的一种方式是使用Runtime.addShutdownHook来注册一个可以closures所有进程的线程(当然你需要保留一些进程对象)。

closures钩子只有在JVM退出时才被调用,所以它应该可以正常工作。

一点点的黑客,但有效的。

Interesting Posts