Java中如何在Windows启动完全脱离JVM的独立进程
问题描述
我希望启动一个进程,使得即使JVM终止,该衍生进程仍能继续运行,即便它正在向STDOUT输出内容。
我首先尝试使用ProcessBuilder,将输出定向到文件,并传入命令:
cmd /c myCmd.exe arg0 arg1
然而,即使关闭所有输入输出流,调用Process#waitFor时,它仍会等待myCmd.exe执行完毕才返回。该进程似乎仍以某种方式关联到JVM(尽管此时JVM终止可能不会影响子进程)。
随后我尝试使用start命令,但它似乎不在系统路径中(未在c:\windows下找到其可执行文件),因此我通过cmd来运行它,传入ProcessBuilder的参数(以空格分隔)变为:
cmd /c start /b myCmd.exe arg0 arg2 >log 2>&1
这样操作后出现以下结果:
Process#waitFor在myCmd.exe执行完毕前就返回。- 似乎需要使用与传入ProcessBuilder不同的日志文件。
- 我发现转义出现异常:若执行echo命令且参数为
^^^^\foo,日志文件中会写入^\foo;若参数为"^^^^\foo",则日志文件中会原样写入"^^^^\foo"。
我的疑问
- 调用
cmd.exe /c start /b是否是正确的做法? - 我在转义处理(即传入ProcessBuilder的内容)上是否有误?由于是通过cmd.exe调用start,是否需要采用不同的转义方式?另外,Windows进程是否支持传入参数数组?
- 我的实现思路是否错误?是否应该调用C语言编写的原生库?若需要,应使用什么库?我不介意通过调用C程序来实现后台进程运行。
解答
1. 调用cmd.exe /c start /b是否正确?
这个方向是对的,但需要修正细节:
start /b确实能让myCmd.exe在后台运行,脱离当前cmd会话的控制,所以cmd /c会在启动进程后立即退出,Process#waitFor提前返回是符合预期的——毕竟你要的就是JVM不用等待子进程结束。- 你遇到的日志文件问题,是因为
>log 2>&1被当前的cmd进程处理了,而非start启动的myCmd.exe。正确的做法是把重定向逻辑交给目标进程,或者用ProcessBuilder的原生重定向方法,避免依赖cmd的语法。
2. 转义处理的问题 & Windows的参数数组支持
Windows的cmd.exe有独立的转义规则,和Java的字符串转义是两层逻辑,很容易踩坑:
- 当你用ProcessBuilder传入嵌套的
cmd /c start命令时,Java会先处理一次字符串转义,然后cmd会再解析一次。比如你写的^^^^\foo,Java转义后变成^^\foo,cmd再转义就成了^\foo;而引号包裹的内容,cmd会忽略内部转义(除了引号本身),所以"^^^^\foo"会原样保留,这都是正常的cmd行为。 - 关于Windows的参数数组:Windows底层的
CreateProcessAPI接收的是完整命令行字符串,而非数组。Java的ProcessBuilder会帮你把参数数组拼接成符合Windows规则的命令行,但嵌套调用cmd /c时,最好把start的参数单独拆分,让ProcessBuilder帮你处理部分转义,比如:
更省心的方式是直接用ProcessBuilder的ProcessBuilder pb = new ProcessBuilder( "cmd.exe", "/c", "start", "/b", "", // 空引号是start的语法要求,避免第一个参数被当作窗口标题 "myCmd.exe", "arg0", "arg2", ">", "log", "2>&1" );redirectOutput和redirectError方法处理日志,完全绕开cmd的重定向语法,也就不用纠结转义了:ProcessBuilder pb = new ProcessBuilder("myCmd.exe", "arg0", "arg2"); pb.redirectOutput(new File("log")); pb.redirectError(new File("log")); pb.start();
3. 是否需要调用原生C库?
完全没必要,纯Java就能实现需求:
- 如果你想彻底让子进程脱离JVM控制,用
cmd /c start /b "" myCmd.exe ...就足够(注意那个空引号,是start命令的必填项,防止参数被误解析为窗口标题)。 - 另一种更直接的方式:直接启动
myCmd.exe,用ProcessBuilder重定向输出后,不要调用waitFor(),让JVM正常退出即可。只要你关闭了子进程的所有输入输出流,JVM终止后子进程会继续运行——之前waitFor()等待子进程结束,是因为cmd /c本身会等待myCmd.exe完成,和JVM无关。 - 如果你非要用原生方案,Windows下可以用
CreateProcessAPI,设置DETACHED_PROCESS标志让进程完全脱离父进程,用JNA或JNI就能调用,但纯Java方案已经足够简洁,没必要额外引入原生依赖。
内容的提问来源于stack exchange,提问作者Luke
相关产品推荐
相关产品推荐

