这一定是我观察到的最奇怪的事情之一。考虑以下 Java 程序:
import java.io.IOException;
public class StrangeError {
public static void main(String[] args) {
try {
Process process = new ProcessBuilder(
"cmd",
"/c",
"\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64 && set"
).start();
process.waitFor();
} catch (IOException|InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
我用 javac StrangeError.java
编译它,将它复制到我运行 Windows Server 2012 R2 的服务器上,然后用 java StrangeError
运行它。
这里是事情开始变得奇怪的地方。程序挂起,等待它生成的进程完成。这不是预期的行为,因为 vcvarsall.bat
脚本应该立即完成以及 set
。
所以我开始玩弄并发现了以下内容:
- 删除
set
会导致vcvarsall.bat
终止 - 删除
vcvarsall.bat
会导致set
终止 - 将
&&
替换为||
会导致一切正确终止 - 将
vcvarsall.bat
复制到桌面上的某个位置并更改路径会导致所有内容正确终止 - A nearly equivalent program使用相同的命令在 Go 中工作正常
- 我得到 this output如果我在 WinDbg 中运行所有内容并在它挂起后中断进程
- 这似乎无法通过 MSVC2013 的
vcvarsall.bat
重现,但也可以通过 Windows 10 上的 MSVC2015 重现
原来的程序到底出了什么问题?如果我将整个命令 (cmd/c "C:\...
) 复制并粘贴到开始-> 运行中,它会立即启动 cmd
并按预期终止。
这是 Java 的错误吗?这是 Windows 的错误吗?
最佳答案
这是 Java 的错误吗?这是 Windows 的错误吗?
这是您代码中的错误。 :-)
默认情况下,使用 ProcessBuilder 对象创建的子进程会将输出重定向到管道,管道的父端可以使用 Process.getInputStream()
获取,如果您代码不使用它。
由于您的代码只是简单地调用了 .waitFor
而没有做任何排空管道的准备,一旦管道的缓冲区溢出,它就会死锁。我相信默认的缓冲区大小是 4,096 字节。在我的机器上,您正在运行的命令的输出是 5,192 字节,但这将根据环境 block 的原始内容而有所不同。 (从它的声音来看,您的环境中的输出长度是临界值,仅略高于限制,因此即使是像更改 VS 版本这样的小变化也会有所不同。)
根据您实际尝试做的事情,有许多可能的解决方案之一是告诉 Java 不要通过管道传输子进程的输出:
import java.io.IOException;
public class StrangeError {
public static void main(String[] args) {
try {
ProcessBuilder processb = new ProcessBuilder(
"cmd",
"/c",
"\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64 && set"
);
processb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process process = processb.start();
process.waitFor();
} catch (IOException|InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
关于java - cmd.exe 意外挂起,具体取决于我使用的文件所在的位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43341086/