我正在开发一个用Java编写的程序,对于某些操作,它使用用户配置的命令行启动外部程序 . 目前它使用 Runtime.exec()
并且不保留 Process
引用(启动的程序是文本编辑器或存档实用程序,因此不需要系统输入/输出/错误流) .
但是这有一个小问题,因为当Java程序退出时,在退出所有启动的程序之前它并没有真正退出 .
如果启动的程序完全独立于启动它们的JVM,我会更喜欢它 .
目标操作系统是多个,Windows,Linux和Mac是最小的,但任何带有JVM的GUI系统都是最理想的(因此实际命令行的用户可配置性) .
有谁知道如何让启动的程序完全独立于JVM执行?
Edit in response to a comment
启动代码如下 . 代码可以启动位于特定行和列的编辑器,也可以启动存档查看器 . 配置的命令行中的引用值被视为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启动程序时才会发生 . 我正在关闭这个问题,因为问题只存在于我的开发环境中; it is not a problem in production . 从其中一个答案中的测试程序,以及我进行的进一步测试,我感到满意的是,任何平台上的程序的任何用户都不会看到这个问题 .
6 回答
如果您发布重现问题所需的最小代码的测试部分,这可能会有所帮助 . 我在Windows和Linux系统上测试了以下代码 .
并在Linux上进行了以下测试:
test.sh看起来像:
以及在Linux上的这个:
并在Windows上测试了这个:
所有这些都启动了他们的预期程序,但Java应用程序没有出现问题 . 我有
java -version
报告的以下Sun版JVM版本:Windows:1.6.0_13-b03
Linux:1.6.0_10-b33
我还没有机会在我的Mac上进行测试 . 也许与项目中的其他代码发生了一些可能不太清楚的交互 . 您可能想尝试此测试应用程序并查看结果 .
您的流程之间存在父子关系,您必须打破它 . 对于Windows,您可以尝试:
对于Linux来说,无论如何,这个过程似乎都是分离的,不需要nohup . 我用
gvim
,midori
和acroread
试了一下 .我认为以平台无关的方式使用Runtime.exec是不可能的 .
适用于POSIX兼容系统:
虽然这个问题已经结束,但我有一些观察可以帮助其他人面临类似的问题 .
当你使用Runtime.getRuntime() . exec()然后忽略你得到的java.lang.Process句柄时(比如原始海报的代码),启动的进程有可能会挂起 .
我在Windows环境中遇到过这个问题,并将问题追溯到stdout和stderr流 . 如果启动的应用程序正在写入这些流,并且这些流的缓冲区已填满,则启动的应用程序在尝试写入流时可能会挂起 . 解决方案是:
捕获Process句柄并不断清空流 - 但如果你想在启动流程后立即终止java应用程序,那么这不是一个可行的解决方案
执行进程调用'cmd /c <>'(仅适用于Windows环境) .
后缀进程命令并使用'command> nul 2>&1'将stdout和stderr流重定向到nul
您想在后台启动该程序,并将其与父项分开 . 我考虑nohup(1) .
我怀疑这需要一个实际的进程分叉 . 基本上,你想要的C等价物是:
问题是你不能在纯Java中做fork() . 我会做的是:
这样JVM仍然不会退出,但是没有GUI,只剩下有限的内存占用 .
我能想到的一种方法是使用Runtime.addShutdownHook来注册一个可以终止所有进程的线程(当然,你需要保留进程对象) .
只有在JVM退出时才会调用shutdown hook,因此它应该可以正常工作 .
一点点黑客但有效 .