Java ProcessBuilder启动进程成功,flush时抛出Stream closed异常
环境信息
- 操作系统:Linux(Java程序运行在Docker容器内)
- Java版本:1.8
- Node版本:10.22.1
问题复现
尝试使用Java ProcessBuilder类基于可执行js文件创建进程,代码如下:
String command = "/node/app/bin/app.js" final ProcessBuilder pb = new ProcessBuilder(command); Process process = pb.start(); OutputStream out = process.getOutputStream(); out.write("\n[:--:]".getBytes()); out.flush();// 该行抛出异常 java.io.IOException: Stream closed
执行pb.start()后提示进程创建成功,调试信息截图如下:
执行out.flush()时抛出异常,错误栈信息:
Caused by: java.io.IOException: Stream closed at java.lang.ProcessBuilder$NullOutputStream.write(ProcessBuilder.java:433) ~[?:1.8.0_292] at java.io.OutputStream.write(OutputStream.java:116) ~[?:1.8.0_292] at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82) ~[?:1.8.0_292] at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140) ~[?:1.8.0_292]
使用的/node/app/bin/app.js文件内容:
#!/usr/bin/env node var path = require('path'); var apJsFile = path.resolve(__dirname, '../app.js'); //app.js 本身可正常运行 require(appJsFile);
异常原因
错误栈中出现ProcessBuilder$NullOutputStream,说明Java尝试向子进程标准输入写入数据时,子进程已经提前退出,JVM会将已退出子进程对应的标准输入流替换为无操作的空输出流,所有写入、刷新操作都会直接抛出Stream closed异常。
子进程提前退出的常见诱因:
- 直接将js脚本路径作为ProcessBuilder的执行命令时,部分Docker精简内核/环境不会自动解析脚本头部的shebang行,加上如果脚本没有配置可执行权限,会导致进程启动后立刻异常终止。
- 未显式配置ProcessBuilder的工作目录,js脚本内的相对路径解析出错,node加载目标app.js文件失败直接退出,根本没有运行到读取标准输入的逻辑。
- 未消费子进程的标准输出、标准错误流,子进程输出缓冲区占满后被系统强制终止。
修复方案
- 显式指定node解释器作为执行命令,不要依赖shebang自动解析,同时配置正确的工作目录,兼容性最高:
// 命令第一个参数为node可执行文件的容器内绝对路径,第二个参数为脚本路径 List<String> command = Arrays.asList("/usr/local/bin/node", "/node/app/bin/app.js"); final ProcessBuilder pb = new ProcessBuilder(command); // 配置脚本运行的工作目录,避免相对路径解析错误 pb.directory(new File("/node/app")); // 将错误流重定向到标准输出,方便统一排查日志 pb.redirectErrorStream(true); Process process = pb.start();
- 启动独立线程消费子进程的输出流,避免缓冲区满导致子进程阻塞退出:
new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { // 按需处理子进程输出,例如打印业务日志 System.out.println("子进程输出:" + line); } } catch (IOException e) { e.printStackTrace(); } }).start();
- 写入标准输入前先确认子进程存活,用完流及时关闭:
if (process.isAlive()) { try (OutputStream out = process.getOutputStream()) { out.write("\n[:--:]".getBytes()); out.flush(); } }
- 前置校验:先进入容器手动执行
node /node/app/bin/app.js,确认脚本本身可以正常常驻、不会秒退,且逻辑中确实存在读取标准输入的处理。
内容的提问来源于stack exchange,提问作者Rando Shtishi
相关产品推荐
相关产品推荐

