PrintStream突然卡住问题求助:调用方法60次后无响应
问题分析与解决方案
这个问题我之前遇到过!核心原因是你没处理子进程的标准输出(stdout)和错误输出(stderr)流,加上没有等待子进程执行完成就继续创建新进程,多次调用后要么子进程因缓冲区满阻塞,要么系统资源耗尽,最终导致主程序卡住。下面给你一步步拆解和修复方案:
1. 必须消费子进程的输出流,避免缓冲区阻塞
当你启动子进程后,它的stdout和stderr有固定大小的缓冲区(通常是几KB)。如果主进程完全不读取这些输出,缓冲区被填满时,子进程会暂停执行,直到缓冲区有空闲空间。这会直接导致子进程无法处理你通过PrintStream写入的输入,进而让主进程在写输入时也被卡住。
你需要启动独立线程异步读取这两个流,避免阻塞主进程的执行逻辑。
2. 等待子进程执行完成,释放系统资源
你的代码写完输入就关闭了输出流,但没有等待子进程执行完毕就返回。多次调用后,系统中会残留大量未结束的子进程,耗尽进程数、内存等资源,最终导致无法创建新进程,程序卡住。
必须调用process.waitFor()等待子进程结束,并且在结束后销毁进程确保资源释放。
修改后的完整代码示例
private void runProcess(String command, int test_number) throws Exception { Process pro = Runtime.getRuntime().exec(command); // 启动线程读取子进程的标准输出,避免缓冲区满阻塞 new Thread(() -> { try (BufferedReader stdoutReader = new BufferedReader( new InputStreamReader(pro.getInputStream()))) { String line; while ((line = stdoutReader.readLine()) != null) { // 可根据需求处理输出,比如记录测试日志 System.out.printf("Test %d | stdout: %s%n", test_number, line); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 启动线程读取子进程的错误输出,同样避免阻塞 new Thread(() -> { try (BufferedReader stderrReader = new BufferedReader( new InputStreamReader(pro.getErrorStream()))) { String line; while ((line = stderrReader.readLine()) != null) { System.err.printf("Test %d | stderr: %s%n", test_number, line); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 写入测试用例输入 try (PrintStream printStream = new PrintStream(pro.getOutputStream())) { for (String testcase_input : testcase_inputs) { printStream.println(testcase_input); printStream.flush(); // 确保输入立即发送给子进程 } } // try-with-resources自动关闭输出流,子进程会收到EOF信号 // 等待子进程执行完成,获取退出码 int exitCode = pro.waitFor(); System.out.printf("Test %d completed with exit code: %d%n", test_number, exitCode); // 强制销毁进程,确保资源完全释放(避免子进程异常挂起) pro.destroy(); }
进阶优化:复用子进程(如果适用)
如果你的场景是多次调用同一个Java程序,强烈建议复用一个子进程,而不是每次都启动新进程。频繁创建进程会带来很大的系统开销,也是60次后卡住的潜在诱因。
可以在程序初始化时启动一次子进程,后续多次复用它的输入输出流:
// 类成员变量,用于复用子进程和流 private Process reusableProcess; private PrintStream reusableInput; private BufferedReader reusableStdout; private BufferedReader reusableStderr; // 初始化复用进程 public void initReusableProcess(String targetCommand) throws Exception { reusableProcess = Runtime.getRuntime().exec(targetCommand); reusableInput = new PrintStream(reusableProcess.getOutputStream()); // 持续读取标准输出的线程 new Thread(() -> { try { reusableStdout = new BufferedReader( new InputStreamReader(reusableProcess.getInputStream())); String line; while ((line = reusableStdout.readLine()) != null) { // 根据测试逻辑处理输出,比如匹配测试结果 System.out.printf("Reusable process | stdout: %s%n", line); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 持续读取错误输出的线程 new Thread(() -> { try { reusableStderr = new BufferedReader( new InputStreamReader(reusableProcess.getErrorStream())); String line; while ((line = reusableStderr.readLine()) != null) { System.err.printf("Reusable process | stderr: %s%n", line); } } catch (IOException e) { e.printStackTrace(); } }).start(); } // 复用进程执行测试用例 private void runTestWithReusableProcess(int test_number) throws Exception { for (String testcase_input : testcase_inputs) { reusableInput.println(testcase_input); reusableInput.flush(); } // 若需要等待本次测试结果,可在这里添加逻辑(比如读取到特定结束标记) } // 程序退出时清理资源 public void cleanupReusableProcess() throws IOException { reusableInput.close(); reusableStdout.close(); reusableStderr.close(); reusableProcess.destroy(); }
总结
你的问题本质是子进程流处理缺失和资源管理不当。按照上面的方法修改后,无论是处理单次进程的流阻塞,还是优化多次调用的资源开销,都能解决60次后卡住的问题。
内容的提问来源于stack exchange,提问作者NGC1977
相关产品推荐
相关产品推荐

