You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何读取Java中运行中Process的stdout/stderr内容?

解决Java Process/ProcessBuilder超时场景下的输出读取问题

核心问题在于同步读取进程输出流的时机错误:

  • 进程未正常退出时,输入流不会自动关闭,readAllAsString会一直阻塞等待更多数据;
  • 调用destroy()后进程流被强制关闭,此时再读取会直接抛出流关闭异常。

最优解决方案:异步提前读取输出流

在调用waitFor超时等待前,就启动异步线程读取进程的输出流并缓存内容。这样无论进程正常结束还是超时,都能拿到已输出的内容,避免阻塞或流关闭异常。

改造后的完整测试代码

@Test
void testProcessExecution() throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder()
            .command(commandLine)
            .directory(new File(directory))
            .redirectErrorStream(true);

    Process process = processBuilder.start();

    // 异步读取输出流,缓存结果
    AtomicReference<String> outputRef = new AtomicReference<>();
    Thread outputReaderThread = new Thread(() -> {
        try {
            outputRef.set(readAllAsString(process.getInputStream()));
        } catch (IOException e) {
            // 捕获流关闭异常,此时进程可能已被destroy,记录异常信息
            outputRef.set("Stream closed prematurely: " + e.getMessage());
        }
    });
    outputReaderThread.start();

    // 等待进程结束或超时
    boolean finished = process.waitFor(10, TimeUnit.SECONDS);
    if (!finished) {
        process.destroy();
        // 等待读取线程完成,确保拿到尽可能完整的输出
        outputReaderThread.join(1000);
        assertThat(false)
                .as("Timed out waiting for process, aborting. Stdout and stderr content:\n\n%s", outputRef.get())
                .isTrue();
    } else {
        // 等待读取线程完成,获取完整输出
        outputReaderThread.join();
        assertThat(process.exitValue())
                .as("Process exited with non-zero exit code. Stdout and stderr content:\n\n%s", outputRef.get())
                .isZero();
    }
}

private String readAllAsString(InputStream inputStream) throws IOException {
    return CharStreams.toString(new InputStreamReader(
            inputStream, StandardCharsets.UTF_8
    ));
}

关键细节说明

  1. 异步读取线程:进程启动后立刻启动线程读取输出流,readAllAsString会在流自然关闭(进程正常退出)或被强制关闭(destroy)时结束,将已读取内容存入AtomicReference。
  2. 线程等待:断言前调用outputReaderThread.join(),确保读取线程完成,拿到完整或尽可能完整的输出;超时场景下设置短等待时间,避免无限阻塞。
  3. 异常处理:读取线程中捕获流关闭异常,避免线程因异常终止导致输出内容丢失。

替代方案:使用Apache Commons Exec库

如果不想手动处理异步线程,可以用Apache Commons Exec库,它封装了进程执行、超时处理和输出读取的逻辑,代码更简洁:

@Test
void testWithCommonsExec() throws Exception {
    CommandLine cmdLine = CommandLine.parse(String.join(" ", commandLine));
    DefaultExecutor executor = new DefaultExecutor();
    executor.setWorkingDirectory(new File(directory));
    executor.setExitValue(0);

    // 设置10秒超时
    ExecuteWatchdog watchdog = new ExecuteWatchdog(10 * 1000);
    executor.setWatchdog(watchdog);

    // 捕获输出内容
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
    executor.setStreamHandler(streamHandler);

    try {
        executor.execute(cmdLine);
    } catch (ExecuteException e) {
        String output = outputStream.toString(StandardCharsets.UTF_8);
        if (watchdog.killedProcess()) {
            assertThat(false)
                    .as("Timed out waiting for process, aborting. Stdout and stderr content:\n\n%s", output)
                    .isTrue();
        } else {
            assertThat(false)
                    .as("Process exited with non-zero exit code. Stdout and stderr content:\n\n%s", output)
                    .isTrue();
        }
    }
}

内容的提问来源于stack exchange,提问作者Per Lundberg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 10:01:30