如何读取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 )); }
关键细节说明
- 异步读取线程:进程启动后立刻启动线程读取输出流,
readAllAsString会在流自然关闭(进程正常退出)或被强制关闭(destroy)时结束,将已读取内容存入AtomicReference。 - 线程等待:断言前调用
outputReaderThread.join(),确保读取线程完成,拿到完整或尽可能完整的输出;超时场景下设置短等待时间,避免无限阻塞。 - 异常处理:读取线程中捕获流关闭异常,避免线程因异常终止导致输出内容丢失。
替代方案:使用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
相关产品推荐
相关产品推荐

