[英]How do I launch a completely independent process from a Java program?
我正在开发一个用 Java 编写的程序,对于某些操作,它使用用户配置的命令行启动外部程序。 目前它使用Runtime.exec()
并且不保留Process
引用(启动的程序是文本编辑器或存档实用程序,因此不需要系统输入/输出/错误流)。
但这有一个小问题,即当 Java 程序退出时,它不会真正退出,直到所有启动的程序都退出。
如果启动的程序完全独立于启动它们的 JVM,我会非常喜欢它。
目标操作系统有多种,Windows、Linux 和 Mac 是最少的,但任何带有 JVM 的 GUI 系统都是真正需要的(因此实际命令行的用户可配置性)。
有谁知道如何使启动的程序完全独立于 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 启动程序时才会发生。 我正在关闭这个问题,因为该问题仅存在于我的开发环境中; 这在生产中不是问题。 从其中一个答案中的测试程序以及我进行的进一步测试来看,我很满意这不是任何平台上该程序的任何用户都会看到的问题。
您的进程之间存在父子关系,您必须打破这种关系。 对于 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();
我有一些观察可以帮助其他面临类似问题的人。
当您使用 Runtime.getRuntime().exec() 然后忽略返回的 java.lang.Process 句柄时(如原始海报中的代码),启动的进程可能会挂起。
我在 Windows 环境中遇到过这个问题,并将问题追溯到 stdout 和 stderr 流。 如果启动的应用程序正在写入这些流,并且这些流的缓冲区已满,则启动的应用程序在尝试写入流时可能会挂起。 解决方法是:
cmd /c <<process>>
执行进程调用(这仅适用于 Windows 环境)。如果您发布重现问题所需的最少代码的测试部分,这可能会有所帮助。 我在 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 的以下版本:
我还没有机会在我的 Mac 上进行测试。 也许在您的项目中与其他代码发生了一些可能不清楚的交互。 您可能想试试这个测试应用程序,看看结果如何。
您想在后台启动程序,并将其与父程序分开。 我会考虑nohup(1) 。
我尝试了这里提到的所有内容,但没有成功。 即使使用 cmd /c start 和重定向流 tu nul,主父 Java 进程也无法退出,直到子线程退出。
对我来说只有一个可靠的解决方案是这样的:
try {
Runtime.getRuntime().exec("psexec -i cmd /c start cmd.cmd");
}
catch (Exception e) {
// handle it
}
我知道这还不清楚,但是来自 SysInternals 的这个小实用程序非常有用且经过验证。 这是链接。
我怀疑这需要一个实际的流程分支。 基本上,你想要的 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 注册一个终止所有进程的线程(当然,您需要将进程对象保留在某处)。
关闭挂钩仅在 JVM 退出时调用,因此它应该可以正常工作。
有点黑客但有效。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.