Java使用Runtime调用shell命令出现死锁无法执行问题求助
问题根因
你遇到的进程泄漏、无法调用shell命令的问题是由Runtime.exec()的使用不当直接导致的,核心原因有3个:
- 仅读取了子进程的标准输出流,完全没有处理标准错误流:如果子进程执行过程中输出错误日志,会把错误流的系统缓冲区占满,子进程会被系统阻塞无法退出,一直残留为挂起进程,越积越多直到耗尽系统进程配额
- 没有等待子进程执行结束,也没有主动释放Process相关资源,出现资源泄漏
- 残留的子进程会导致对应的业务线程一直卡在
readLine()方法,最终把Quartz线程池占满,所有调度任务都阻塞等待
修复方案
你需要按照以下逻辑调整代码,同时处理两个输出流、加超时防护、自动释放资源:
Process process = null; BufferedReader stdoutReader = null; try { process = Runtime.getRuntime().exec(cmdArray); // 分别处理标准输出和标准错误流,避免缓冲区满导致进程阻塞 final InputStream stdout = process.getInputStream(); final InputStream stderr = process.getErrorStream(); // 异步读取错误流 final StringBuilder errorSb = new StringBuilder(); new Thread(() -> { try (BufferedReader errReader = new BufferedReader(new InputStreamReader(stderr))) { String errLine; while ((errLine = errReader.readLine()) != null) { errorSb.append(errLine).append("\r\n"); } } catch (Exception e) { // 按需打印错误日志即可 } }).start(); // 主线程读取标准输出 stdoutReader = new BufferedReader(new InputStreamReader(stdout)); String line; while ((line = stdoutReader.readLine()) != null) { resultBuilder.append(line).append("\r\n"); } // 等待进程退出,设置超时时间(示例为300秒,可根据业务调整),避免进程永久挂起 boolean exitNormal = process.waitFor(300, TimeUnit.SECONDS); if (!exitNormal) { // 超时强制销毁进程 process.destroyForcibly(); // 按需添加超时告警/日志逻辑 } // 可选:如果错误流有内容可以做异常判断或日志打印 String errorLog = errorSb.toString(); if (errorLog.length() > 0) { // 按需处理错误输出 } } catch (Exception e) { // 处理业务异常 } finally { // 手动释放所有资源 if (stdoutReader != null) { try { stdoutReader.close(); } catch (Exception ignore) {} } if (process != null) { try { process.destroyForcibly(); } catch (Exception ignore) {} } }
额外优化建议
- Java 9+推荐直接使用
ProcessBuilder替代Runtime.exec(),可以直接调用redirectErrorStream(true)把标准错误流合并到标准输出流,不需要单独开线程处理错误流,代码更简洁 - 不要在调度任务中设置过长的超时时间,避免线程被长期占用
- 上线后可以通过
ps -efT | grep 你的Java进程ID验证线程数是否正常,ps -ef | grep 子进程关键字验证是否还有残留进程
内容的提问来源于stack exchange,提问作者jdragon
相关产品推荐
相关产品推荐

