我正在开发一个用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启动程序时才会出现这种情况。我正在关闭这个问题,因为问题只存在于我的开发环境中; 这不是生产中的问题。从其中一个答案中的测试程序,以及我已经进行的进一步测试,我感到满意的是,任何平台上的任何用户都不会看到这个问题。
答案 0 :(得分:28)
您的流程之间存在父子关系,您必须打破它。 对于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();
答案 1 :(得分:21)
虽然这个问题已经结束,但我有一些观察可以帮助其他人面临类似的问题。
当你使用Runtime.getRuntime()。exec()然后忽略你得到的java.lang.Process句柄时(比如原始海报的代码),启动的进程可能会挂起。
我在Windows环境中遇到过这个问题,并将问题追溯到stdout和stderr流。如果启动的应用程序正在写入这些流,并且这些流的缓冲区已填满,则启动的应用程序在尝试写入流时可能会挂起。解决方案是:
答案 2 :(得分:17)
如果您发布重现问题所需的最小代码的测试部分,则可能会有所帮助。我在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上进行测试。也许与项目中的其他代码发生了一些可能不太清楚的交互。您可能想尝试这个测试应用程序并查看结果。
答案 3 :(得分:1)
您想在后台启动该程序,并将其与父程序分开。我考虑nohup(1)。
答案 4 :(得分:0)
我怀疑这需要一个实际的进程fork。基本上,你想要的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,只剩下有限的内存空间。
答案 5 :(得分:0)
我尝试了这里提到的所有内容,但没有成功。直到使用cmd / c start和重定向流tunul退出子线程,主父Java进程才能退出。
对我来说,唯一可靠的解决方案是:
try {
Runtime.getRuntime().exec("psexec -i cmd /c start cmd.cmd");
}
catch (Exception e) {
// handle it
}
我知道这还不清楚,但是SysInternals的这个小实用程序非常有用并得到了证明。 Here是链接。
答案 6 :(得分:-1)
我能想到的一种方法是使用Runtime.addShutdownHook来注册一个杀死所有进程的线程(当然,你需要在某处保留进程对象)。
只有在JVM退出时才会调用shutdown hook,因此它应该可以正常工作。
一点点黑客但有效。