Runtime.getRuntime().exec常见问题

本文探讨了Java程序调用Perl脚本时遇到的问题及解决方案,重点介绍了如何避免因未及时处理输出流而导致的进程挂起现象。

今天搞了一天,JAVA调用一个PERL程序,得不得就退不出,千试万试,LOG精细到逐行,知道在哪停住了,但打死不知道为什么。
后来吃个饭都放弃了,居然又找到答案,要没看到它,那真以为里面有鬼了。

大概原因是,调用Runtime.getRuntime().exec后,如果不及时捕捉进程的输出,会导致JAVA挂住,看似被调用进程没退出。所以,解决办法是,启动进程后,再启动两个JAVA线程及时的把被调用进程的输出截获。

一下子,整个世界清爽多了。。。多谢这么仁兄,下面转一下:



转自:http://pudding.sharera.com/blog/BlogTopic/31232.htm

碰到一个项目需要从Java中运行Perl程序,这个Perl程序调用客户的Web service,每次发送一个请求,接受一个响应。Java程序中包含多个请求,需要多次调用Perl程序,并且接受和解析响应(这个烂设计可不是我干 的,我实在不明白强大的Java Web Service为什么要弄成这样,不过客户是老大)。使用Java Runtime的exec()方法,发现运行一段时间后,进程就被挂起了(之前的响应完全正确)。于是分析原因,发现我在运行exec()方法后,立刻执 行了Process的waitFor()方法,这里出了问题。在网上找到一篇文章讲述这个问题:
地址:http://brian.pontarelli.com/2005/11/11/java-runtime-exec-can-hang/

Java Runtime exec can hang

November 11, 2005 on 4:40 pm | In Java |

The next version of Savant is going to focus heavily on the stand-alone runtime and support for dialects and plugins. Supporting all that is largely handled by using a simple executor framework I wrote around Java 1.4 and lower’s Runtime.exec method. A few things to keep in mind when using this:

  1. Always read from the streams prior to calling waitFor. Otherwise you could end up waiting forever on Windows and other OS platforms whose I/O buffers can’t store enough from standard out and standard error to ensure the program has finished. These platforms will pause the execution of whatever is running until something reads the buffered content from standard out and standard error. I would imagine all platforms suffer from this, but some platforms have larger buffers than others. Needless to say, always read from the streams first.
  2. Always read from standard error first. I ran across a bug where some OS platforms will always open standard out, but never close it. What this means is that if you read from standard out first and the process only writes to standard error, you’ll hang forever waiting to read. If you read from standard error first, you’ll always be okay on these platforms because the OS seems to shutdown standard error. I think however, that the best way to handle all cases is to check both standard error and standard out for readiness and only read from them if they have something to offer. The downside I could see here is that error isn’t ready, but eventually will be.

可以看出:

  • 永远要在调用waitFor()方法之前读取数据流
  • 永远要先从标准错误流中读取,然后再读取标准输出流

于是将waitFor()方法放在读取数据流后调用,目前没有发现什么问题。

正好解决了我心中的疑问,非常感谢!

我们的程序一开始就是exec完了接着waitFor(),但bat文件执行不完整:

Process proc = Runtime.getRuntime().exec(cmd);
                proc.waitFor();

后面的build中在waitFor()之前读取了数据流,bat文件就可以完整执行了:

Process proc = Runtime.getRuntime().exec(cmd);
     StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "Error");           
                 StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "Output");
                 errorGobbler.start();
                 outputGobbler.start();

     proc.waitFor();

class StreamGobbler extends Thread {
 InputStream is;

 String type;

 StreamGobbler(InputStream is, String type) {
  this.is = is;
  this.type = type;
 }

 public void run() {
  try {
   InputStreamReader isr = new InputStreamReader(is);
   BufferedReader br = new BufferedReader(isr);
   String line = null;
   while ((line = br.readLine()) != null) {
    if (type.equals("Error"))
     LogManager.logError(line);
    else
     LogManager.logDebug(line);
   }
  } catch (IOException ioe) {
   ioe.printStackTrace();
  }
 }
}

TestPrint.bat:

echo P1=%1  >D:"2.1.2env"2.1.2home"CompuSet"output"TestPrint.log
echo P2=%2 >>D:"2.1.2env"2.1.2home"CompuSet"output"TestPrint.log
echo P3=%3 >>D:"2.1.2env"2.1.2home"CompuSet"output"TestPrint.log
echo P4=%4 >>D:"2.1.2env"2.1.2home"CompuSet"output"TestPrint.log
echo P5=%5 >>D:"2.1.2env"2.1.2home"CompuSet"output"TestPrint.log
echo P6=%6 >>D:"2.1.2env"2.1.2home"CompuSet"output"TestPrint.log

Bad_TestPrint.log:

P1=C:"xPression"CompuSet"output"MartyTestOut1.afp 
P2=Literal1
P3="Rick Skinner"
P4=Parameter3

Good_TestPrint.log

P1=C:"xPression"CompuSet"output"MartyTestOut1.afp 
P2=Literal1
P3="Rick Skinner"
P4=Parameter3
P5=Parameter4
P6=Parameter5

<think> 我们正在讨论Java中的Runtime.getRuntime().exec()方法。根据引用内容,我们已经知道这个方法用于执行外部命令或程序,并返回一个Process对象。同时,我们也注意到在使用过程中可能会遇到阻塞问题。下面,我将按照用户的要求,详细解释这个方法的使用方法、作用、注意事项等,并确保遵守格式要求。 ### 一、Runtime.getRuntime().exec() 基本介绍 `Runtime.getRuntime().exec()` 方法是Java中用于执行外部命令的API。它通过创建一个子进程来运行指定的命令,并返回一个`Process`对象,通过该对象可以控制子进程并获取其信息[^3]。基本用法如下: ```java Process process = Runtime.getRuntime().exec("notepad.exe"); ``` 上述代码会启动Windows系统中的记事本程序。 ### 二、exec()方法的重载形式 `exec()`方法有多个重载版本,以适应不同的调用需求[^3]: 1. **执行单个命令字符串**: ```java Process exec(String command) ``` 例如:`exec("ping www.baidu.com")` 2. **拆分命令和参数**: ```java Process exec(String[] cmdarray) ``` 例如:`exec(new String[]{"ping", "www.baidu.com"})` 3. **指定环境变量**: ```java Process exec(String command, String[] envp) Process exec(String[] cmdarray, String[] envp) ``` 4. **指定工作目录**: ```java Process exec(String[] cmdarray, String[] envp, File dir) ``` 其中`dir`参数指定子进程的工作目录。 ### 三、Process对象的使用 执行外部命令后返回的`Process`对象提供了以下关键方法: 1. **等待进程结束**: ```java int exitCode = process.waitFor(); // 阻塞当前线程直到子进程结束[^1] ``` 返回值为子进程的退出码,通常0表示成功。 2. **获取输入/输出流**: - `InputStream getInputStream()`:获取子进程的标准输出流(即子进程的输出) - `InputStream getErrorStream()`:获取子进程的错误输出流 - `OutputStream getOutputStream()`:获取子进程的标准输入流(即向子进程输入数据) 3. **销毁进程**: ```java process.destroy(); // 强制终止子进程 ``` ### 四、常见问题与解决方案 #### 1. 阻塞问题 当子进程产生大量输出时,如果主进程不读取这些输出,缓冲区可能会被填满,导致子进程阻塞,进而使主进程的`waitFor()`调用一直等待[^2]。解决方案是及时处理输入流和错误流: ```java Process process = Runtime.getRuntime().exec(command); // 启动两个线程分别处理标准输出和错误输出 new Thread(() -> { try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { // 处理标准输出 } } catch (IOException e) { e.printStackTrace(); } }).start(); new Thread(() -> { try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getErrorStream()))) { String line; while ((line = reader.readLine()) != null) { // 处理错误输出 } } catch (IOException e) { e.printStackTrace(); } }).start(); int exitCode = process.waitFor(); // 此时不会阻塞 ``` #### 2. 命令注入风险 直接拼接用户输入作为命令参数可能导致命令注入攻击: ```java // 危险示例:用户输入可能包含恶意命令 String userInput = "file; rm -rf /"; Runtime.getRuntime().exec("ls -l " + userInput); ``` 应使用参数数组形式并验证用户输入: ```java String[] cmd = {"ls", "-l", sanitizedUserInput}; ``` #### 3. 跨平台兼容性 不同操作系统的命令语法不同(如`dir`在Windows,`ls`在Linux),应确保命令适用于目标环境: ```java String command = System.getProperty("os.name").toLowerCase().contains("win") ? "cmd /c dir" : "ls -l"; ``` ### 五、数学表达式的使用示例 在讨论进程退出时,我们可以用数学表达式描述退出码的判断: $$ \text{exitCode} = \begin{cases} 0 & \text{表示成功} \\ \text{非零值} & \text{表示错误} \end{cases} $$ ### 六、最佳实践总结 1. 始终处理子进程的输入流、输出流和错误流 2. 使用`waitFor()`时配合流处理线程 3. 优先使用参数数组形式避免注入风险 4. 检查命令的跨平台兼容性 §§相关问题§§ 1. 如何在Java中捕获并实时显示外部命令的执行输出? 2. 使用`Runtime.exec()`执行长时间运行的进程时如何避免内存泄漏? 3. Java的`ProcessBuilder`与`Runtime.exec()`有何区别? 4. 如何安全地在`Runtime.exec()`中执行包含管道的命令(如`ps aux | grep java`)? [^1]: Java Process类Runtime.getRuntime().exec() 执行bat脚本程序 [^2]: java Runtime.getRuntime().exec阻塞卡死问题解决 [^3]: java_Runtime.getRuntime().exec()介绍 [^4]: java中Runtime.getRuntime()学习 [^5]: java中Runtime.getRuntime().exec()的坑,会造成阻塞的解决 </think> ### Java中`Runtime.getRuntime().exec()`详解 #### 一、基本作用与原理 `Runtime.getRuntime().exec()`是Java执行外部命令的核心API,其核心机制为: 1. **创建子进程**:在操作系统层面创建独立进程执行命令 $$ \text{Process} = \text{Runtime} \rightarrow \text{exec}(\text{command}) $$ 2. **进程管理**:返回`Process`对象用于控制子进程[^1][^3] 3. **非阻塞调用**:默认异步执行(需手动等待完成) #### 二、使用方法 ##### 1. 基础命令执行 ```java // 执行记事本程序 Process process = Runtime.getRuntime().exec("notepad.exe"); process.waitFor(); // 阻塞等待完成[^1] ``` ##### 2. 带参数执行 ```java // 执行Python脚本(参数数组形式更安全) String[] cmd = {"python", "script.py", "arg1"}; Process p = Runtime.getRuntime().exec(cmd); ``` ##### 3. 获取执行结果 ```java try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } int exitCode = process.waitFor(); // 获取退出码[^5] ``` #### 三、关键注意事项 ##### 1. 阻塞问题解决方案 当子进程输出缓冲区满时会导致父进程阻塞[^2][^5]: ```java // 必须消费输入流和错误流 new Thread(() -> { try (BufferedReader errorReader = new BufferedReader( new InputStreamReader(process.getErrorStream()))) { while (errorReader.readLine() != null); // 清空错误流 } catch (IOException e) { /* 处理异常 */ } }).start(); ``` ##### 2. 跨平台风险 命令语法差异可能导致异常: ```java // 推荐做法:根据OS选择命令 String cmd = System.getProperty("os.name").startsWith("Windows") ? "cmd /c dir" : "ls -l"; ``` ##### 3. 资源释放 必须关闭流并销毁进程: ```java process.getInputStream().close(); process.destroy(); // 强制终止进程[^4] ``` ##### 4. 超时控制 防止进程无限挂起: ```java if (!process.waitFor(30, TimeUnit.SECONDS)) { process.destroyForcibly(); // 超时强制终止 } ``` #### 四、与ProcessBuilder对比 | **特性** | `Runtime.exec()` | `ProcessBuilder` | |----------------|----------------------------|---------------------------| | 链式调用 | ❌ 不支持 | ✔️ 支持 | | 重定向 | 手动处理流 | `redirectOutput()`方法 | | 环境变量控制 | 有限支持 | 完整环境控制 | | 工作目录设置 | 需单独处理 | `directory()`方法 | > ⚠️ **最佳实践**:新代码推荐使用`ProcessBuilder`,其提供更安全的进程控制: > ```java > Process p = new ProcessBuilder("cmd", "/c", "echo hello") > .redirectErrorStream(true) > .start(); > ``` #### 五、典型应用场景 1. **批处理操作**:执行`.bat`/`.sh`脚本[^1] 2. **系统管理**:获取硬件信息(如`wmic memorychip`) 3. **跨语言调用**:启动Python/Node.js等外部程序 4. **定时任务**:配合调度框架执行外部作业[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值