runtime.getruntime().exec() 卡住或拿不到输出的根本原因是子进程继承父jvm流但缓冲区不自动刷新且java未主动读取,导致死锁;必须显式消费输入/错误流,避免直接获取输出流、正确传参防注入、windows需用cmd.exe/powershell.exe显式调用,推荐改用功能更安全的processbuilder。

Runtime.getRuntime().exec() 为什么经常卡住或拿不到输出
根本原因是 exec() 启动的进程会继承父 JVM 的标准输入、输出、错误流,但这些流的缓冲区不自动刷新,子进程一写满就阻塞;同时 Java 不主动读取,导致死锁。
常见现象:Process.waitFor() 一直不返回、process.getInputStream().read() 阻塞、脚本明明执行完了但 Java 程序没感知。
- 必须显式消费
process.getInputStream()和process.getErrorStream(),哪怕只是丢弃(用Thread单独读或InputStream.readAllBytes()) - 避免直接调用
process.getOutputStream()—— 大多数外部命令不需要输入,强行获取反而可能触发阻塞 - 别用
String[] cmdarray直接传带空格的路径,比如["/opt/my app/start.sh"]会被拆成两个参数;改用new String[]{"/bin/sh", "-c", "/opt/my app/start.sh"}
如何安全传参并防止 shell 注入
直接拼接字符串进 exec() 是高危操作,尤其参数来自用户输入时,rm -rf / 类命令可能被注入执行。
正确做法是绕过 shell 解析,让 exec() 直接调用程序,参数作为独立数组元素传入。
立即学习“Java免费学习笔记(深入)”;
- ✅ 安全:
Runtime.getRuntime().exec(new String[]{"ls", "-l", "/tmp/user-" + userId}) - ❌ 危险:
Runtime.getRuntime().exec("ls -l /tmp/user-" + userId)—— 若userId = "123; rm -rf /"就完蛋 - 需要 shell 特性(管道、重定向、通配符)时,显式调用
/bin/sh并严格过滤参数,例如:new String[]{"/bin/sh", "-c", "find "$1" -name "*.log"", "_", "/var/log"},其中"$1"是位置参数,"_"占位,"/var/log"是实际值
Windows 下执行 .bat 或 PowerShell 脚本的坑
Windows 没有默认的 POSIX shell,直接 exec "xxx.bat" 很可能报 java.io.IOException: Cannot run program "xxx.bat": CreateProcess error=193, %1 is not a valid Win32 application。
- 执行 .bat/.cmd 必须通过
cmd.exe /c:new String[]{"cmd.exe", "/c", "C:\path\to\script.bat"} - 执行 PowerShell 脚本要加
-ExecutionPolicy Bypass绕过策略限制:new String[]{"powershell.exe", "-ExecutionPolicy", "Bypass", "-File", "C:\script.ps1"} - 路径含中文或空格时,PowerShell 的
-File参数要求路径加双引号,但 Java 数组里不能写""C:\a b\s.ps1""—— 正确方式是让 PowerShell 自己处理:new String[]{"powershell.exe", "-ExecutionPolicy", "Bypass", "-Command", "& 'C:\a b\s.ps1'"}
替代方案:为什么现在更推荐 ProcessBuilder
ProcessBuilder 不是语法糖,它解决了 Runtime.exec() 的多个设计缺陷:环境变量隔离、工作目录设置、命令自动拆分、错误提示更明确。
- 设置工作目录:
new ProcessBuilder("python", "app.py").directory(new File("/opt/myapp")) - 修改环境变量(不影响当前 JVM):
pb.environment().put("DEBUG", "1") - 自动处理空格路径:
new ProcessBuilder("C:\Program Files\Java\bin\java.exe", "-version")不会因空格出错 - 启动失败时抛
IOException带具体原因(如 “No such file or directory”),而exec()可能静默失败
真正复杂的命令调度,最终还是要落到 ProcessBuilder 上 —— Runtime.exec() 的存在更多是为了兼容旧代码,不是首选。










