Runtime.getRuntime().exec()执行JVM之外的程序:常见的几种陷阱

前言

日常java开发中,有时需要通过java运行其它应用功程序,比如shell命令等。jdk的Runtime类提供了这样的方法。首先来看Runtime类的文档, 从文档中可以看出,每个java程序只会有一个Runtime实例,显然这是一个单例模式。

/**

* Every Java application has a single instance of class

* Runtime that allows the application to interface with

* the environment in which the application is running. The current

* runtime can be obtained from the getRuntime method.

*

* An application cannot create its own instance of this class.

*/

public class Runtime {

private static Runtime currentRuntime = new Runtime();

/**

* Returns the runtime object associated with the current Java application.

* Most of the methods of class Runtime are instance

* methods and must be invoked with respect to the current runtime object.

*

* @return the Runtime object associated with the current

* Java application.

*/

public static Runtime getRuntime() {

return currentRuntime;

}

/** Don't let anyone else instantiate this class */

private Runtime() {}

......

}

要运行JVM中外的程序,Runtime类提供了如下方法,详细使用方法可参见源码注释

public Process exec(String command) throws IOException

public Process exec(String cmdarray[]) throws IOException

public Process exec(String command, String[] envp) throws IOException

public Process exec(String command, String[] envp, File dir) throws IOException

public Process exec(String[] cmdarray, String[] envp) throws IOException

public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException

通过这种方式运行外部程序,有几个陷阱需要注意,本文尝试总结常见的几个陷阱,并给出相应的解决方法。同时封装一种比较完善的工具类,用来运行外部应用,并提供超时功能。

Runtime.exec()常见的几种陷阱以及避免方法

陷阱1:IllegalThreadStateException

通过exec执行java命令为例子,最简单的方式如下。执行exec后,通过Process获取外部进程的返回值并输出。

import java.io.IOException;

/**

* Created by yangjinfeng02 on 2016/4/27.

*/

public class Main {

public static void main(String[] args) {

Runtime runtime = Runtime.getRuntime();

try {

Process process = runtime.exec("java");

int exitVal = process.exitValue();

System.out.println("process exit value is " + exitVal);

} catch (IOException e) {

e.printStackTrace();

}

}

}

很遗憾的是,我们发现输出结果如下,抛出了IllegalThreadStateException异常

Exception in thread "main" java.lang.IllegalThreadStateException: process has not exited

at java.lang.ProcessImpl.exitValue(ProcessImpl.java:443)

at com.baidu.ubqa.agent.runner.Main.main(Main.java:18)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

为什么会抛出IllegalThreadStateException异常?

这是因为外部线程还没有结束,这个时候去获取退出码,exitValue()方法抛出了异常。看到这里读者可能会问,为什么这个方法不能阻塞到外部进程结束后再返回呢?确实如此,Process有一个waitFor()方法,就是这么做的,返回的也是退出码。因此,我们可以用waitFor()方法替换exitValue()方法。

陷阱2:Runtime.exec()可能hang住,甚至死锁

首先看下Process类的文档说明

*

By default, the created subprocess does not have its own terminal

* or console. All its standard I/O (i.e. stdin, stdout, stderr)

* operations will be redirected to the parent process, where they can

* be accessed via the streams obtained using the methods

* {@link #getOutputStream()},

* {@link #getInputStream()}, and

* {@link #getErrorStream()}.

* The parent process uses these streams to feed input to and get output

* from the subprocess. Because some native platforms only provide

* limited buffer size for standard input and output streams, failure

* to promptly write the input stream or read the output stream of

* the subprocess may cause the subprocess to block, or even deadlock.

从这里可以看出,Runtime.exec()创建的子进程公用父进程的流,不同平台上,父进程的stream buffer可能被打满导致子进程阻塞,从而永远无法返回。

针对这种情况,我们只需要将子进程的stream重定向出来即可。

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

/**

* Created by yangjinfeng02 on 2016/4/27.

*/

public class Main {

public static void main(String[] args) {

Runtime runtime = Runtime.getRuntime();

try {

Process process = runtime.exec("java");

BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));

BufferedReader stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));

String line;

System.out.println("OUTPUT");

while ((line = stdoutReader.readLine()) != null) {

System.out.println(line);

}

System.out.println("ERROR");

while ((line = stderrReader.readLine()) != null) {

System.out.println(line);

}

int exitVal = process.waitFor();

System.out.println("process exit value is " + exitVal);

} catch (IOException e) {

e.printStackTrace();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

陷阱3:不同平台上,命令的兼容性

如果要在windows平台上运行dir命令,如果直接指定命令参数为dir,会提示命令找不到。而且不同版本windows系统上,运行改命令的方式也不一样。对这宗情况,需要根据系统版本进行适当区分。

String osName = System.getProperty("os.name" );

String[] cmd = new String[3];

if(osName.equals("Windows NT")) {

cmd[0] = "cmd.exe" ;

cmd[1] = "/C" ;

cmd[2] = args[0];

} else if(osName.equals("Windows 95")) {

cmd[0] = "command.com" ;

cmd[1] = "/C" ;

cmd[2] = args[0];

}

Runtime rt = Runtime.getRuntime();

Process proc = rt.exec(cmd);

陷阱4:错把Runtime.exec()的command参数当做命令行

本质上来讲,Runtime.exec()的command参数只是一个可运行的命令或者脚本,并不等效于Shell解器或者Cmd.exe,如果你想进行输入输出重定向,pipeline等操作,则必须通过程序来实现。不能直接在command参数中做。例如,下面的例子

Process process = runtime.exec("java -version > a.txt");

这样并不会产出a.txt文件。要达到这种目的,需通过编程手段实现,如下

import java.io.BufferedReader;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.io.PrintWriter;

/**

* Created by yangjinfeng02 on 2016/4/27.

*/

class StreamGobbler extends Thread {

InputStream is;

String type;

OutputStream os;

StreamGobbler(InputStream is, String type) {

this(is, type, null);

}

StreamGobbler(InputStream is, String type, OutputStream redirect) {

this.is = is;

this.type = type;

this.os = redirect;

}

public void run() {

try {

PrintWriter pw = null;

if (os != null)

pw = new PrintWriter(os);

InputStreamReader isr = new InputStreamReader(is);

BufferedReader br = new BufferedReader(isr);

String line;

while ((line = br.readLine()) != null) {

if (pw != null)

pw.println(line);

System.out.println(type + ">" + line);

}

if (pw != null)

pw.flush();

} catch (IOException ioe) {

ioe.printStackTrace();

}

}

}

public class Main {

public static void main(String args[]) {

try {

FileOutputStream fos = new FileOutputStream("logs/a.log");

Runtime rt = Runtime.getRuntime();

Process proc = rt.exec("cmd.exe /C dir");

// 重定向输出流和错误流

StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "ERROR");

StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "OUTPUT", fos);

errorGobbler.start();

outputGobbler.start();

int exitVal = proc.waitFor();

System.out.println("ExitValue: " + exitVal);

fos.flush();

fos.close();

} catch (Throwable t) {

t.printStackTrace();

}

}

}

一个比较完善的工具类

下面提供一种比较完善的实现,提供了超时功能。

封装返回结果

/**

* ExecuteResult.java

*/

import lombok.Data;

import lombok.ToString;

@Data

@ToString

public class ExecuteResult {

private int exitCode;

private String executeOut;

public ExecuteResult(int exitCode, String executeOut) {

this.exitCode = exitCode;

this.executeOut = executeOut;

}

}

对外接口

/**

* LocalCommandExecutor.java

*/

public interface LocalCommandExecutor {

ExecuteResult executeCommand(String command, long timeout);

}

StreamGobbler类,用来完成stream的管理

/**

* StreamGobbler.java

*/

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class StreamGobbler extends Thread {

private static Logger logger = LoggerFactory.getLogger(StreamGobbler.class);

private InputStream inputStream;

private String streamType;

private StringBuilder buf;

private volatile boolean isStopped = false;

/**

* @param inputStream the InputStream to be consumed

* @param streamType the stream type (should be OUTPUT or ERROR)

*/

public StreamGobbler(final InputStream inputStream, final String streamType) {

this.inputStream = inputStream;

this.streamType = streamType;

this.buf = new StringBuilder();

this.isStopped = false;

}

/**

* Consumes the output from the input stream and displays the lines consumed

* if configured to do so.

*/

@Override

public void run() {

try {

// 默认编码为UTF-8,这里设置编码为GBK,因为WIN7的编码为GBK

InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "GBK");

BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

String line = null;

while ((line = bufferedReader.readLine()) != null) {

this.buf.append(line + "\n");

}

} catch (IOException ex) {

logger.trace("Failed to successfully consume and display the input stream of type " + streamType + ".", ex);

} finally {

this.isStopped = true;

synchronized (this) {

notify();

}

}

}

public String getContent() {

if (!this.isStopped) {

synchronized (this) {

try {

wait();

} catch (InterruptedException ignore) {

ignore.printStackTrace();

}

}

}

return this.buf.toString();

}

}

实现类

通过SynchronousQueue队列保证只有一个线程在获取外部进程的退出码,由线程池提供超时功能。

/**

* LocalCommandExecutorImpl.java

*/

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.io.Closeable;

import java.io.IOException;

import java.io.InputStream;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Future;

import java.util.concurrent.SynchronousQueue;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.TimeoutException;

public class LocalCommandExecutorImpl implements LocalCommandExecutor {

static final Logger logger = LoggerFactory.getLogger(LocalCommandExecutorImpl.class);

static ExecutorService pool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3L, TimeUnit.SECONDS,

new SynchronousQueue());

public ExecuteResult executeCommand(String command, long timeout) {

Process process = null;

InputStream pIn = null;

InputStream pErr = null;

StreamGobbler outputGobbler = null;

StreamGobbler errorGobbler = null;

Future executeFuture = null;

try {

logger.info(command.toString());

process = Runtime.getRuntime().exec(command);

final Process p = process;

// close process's output stream.

p.getOutputStream().close();

pIn = process.getInputStream();

outputGobbler = new StreamGobbler(pIn, "OUTPUT");

outputGobbler.start();

pErr = process.getErrorStream();

errorGobbler = new StreamGobbler(pErr, "ERROR");

errorGobbler.start();

// create a Callable for the command's Process which can be called by an Executor

Callable call = new Callable() {

public Integer call() throws Exception {

p.waitFor();

return p.exitValue();

}

};

// submit the command's call and get the result from a

executeFuture = pool.submit(call);

int exitCode = executeFuture.get(timeout, TimeUnit.MILLISECONDS);

return new ExecuteResult(exitCode, outputGobbler.getContent());

} catch (IOException ex) {

String errorMessage = "The command [" + command + "] execute failed.";

logger.error(errorMessage, ex);

return new ExecuteResult(-1, null);

} catch (TimeoutException ex) {

String errorMessage = "The command [" + command + "] timed out.";

logger.error(errorMessage, ex);

return new ExecuteResult(-1, null);

} catch (ExecutionException ex) {

String errorMessage = "The command [" + command + "] did not complete due to an execution error.";

logger.error(errorMessage, ex);

return new ExecuteResult(-1, null);

} catch (InterruptedException ex) {

String errorMessage = "The command [" + command + "] did not complete due to an interrupted error.";

logger.error(errorMessage, ex);

return new ExecuteResult(-1, null);

} finally {

if (executeFuture != null) {

try {

executeFuture.cancel(true);

} catch (Exception ignore) {

ignore.printStackTrace();

}

}

if (pIn != null) {

this.closeQuietly(pIn);

if (outputGobbler != null && !outputGobbler.isInterrupted()) {

outputGobbler.interrupt();

}

}

if (pErr != null) {

this.closeQuietly(pErr);

if (errorGobbler != null && !errorGobbler.isInterrupted()) {

errorGobbler.interrupt();

}

}

if (process != null) {

process.destroy();

}

}

}

private void closeQuietly(Closeable c) {

try {

if (c != null) {

c.close();

}

} catch (IOException e) {

logger.error("exception", e);

}

}

}

java runtime shell_java Runtime.exec()执行shell/cmd命令:常见的几种陷阱与一种完善实现...相关推荐

  1. 解决java使用Runtime.exec执行linux复杂命令不成功问题

    解决java使用Runtime.exec执行linux复杂命令不成功问题 参考文章: (1)解决java使用Runtime.exec执行linux复杂命令不成功问题 (2)https://www.cn ...

  2. 【莹伙丛】我是如何设置 IDEA 以方便执行shell 脚本命令的?

    [莹伙丛]我是如何设置 IDEA 以方便执行shell 脚本命令的? 前言 如何设置 其他 前言 鉴于目前的办公电脑使用的 WIN10 ,安装的windows 版本的 IDEA . 在 IDEA 中有 ...

  3. docker exec执行多个命令详解

    2019独角兽企业重金招聘Python工程师标准>>> docker exec执行多个命令详解 2018年04月23日 22:46:24 阅读数:6928 标签: dockercon ...

  4. php exec执行多条命令,小技巧:在PHP中调用多条shell指令

    原标题:小技巧:在PHP中调用多条shell指令 有时候,在持续集成的过程中,需要通过网页在目标服务器上执行shell指令,今天就列举一个实例进行讲解. 比如,我们需要能通过网页将SVN update ...

  5. 常用python执行shell的命令

    一.os.system("pwd") 1. 返回值依赖于系统,程序阻塞等待返回 直接返回系统的调用返回值 windows.Linux下是不一样的 2.样例 二.os.popen(c ...

  6. c++ 调用cmd命令行函数 可隐藏黑框 四种方法总结

    在很多情况下,不得不调用cmd命令行,去实现一系列功能,总结靠谱方法两种如下: 方法一:可接受cmd命令行黑框一闪(推荐星数::可传值几乎任何cmd命令) string string getCmdRe ...

  7. 在apk 中执行Runtime.getRuntime().exec adb shell各种命令远程控制其他Android设备(一)

    在手机中可以运行adb命令来远程连接操作其他的Android设备(包括手机.智能电视) 前提,两台设备必须连入同一个局域网络中. 参考方法: public void execShell(String ...

  8. Java如何执行操作系统的CMD命令行

    在模拟cmd调用Python时遇到一些情况,这类问题可以归类为"超时,阻塞"等,问题原因: Process p=Runtime.getRuntime().exec(String[] ...

  9. linux c之通过popen和pclose函数创建管道执行shell 运行命令使用总结

    1.函数介绍 popen 和 pclose 函数 操作是创建一个管道链接到另一个进程,然后读其输出或向其输入端发送数据.标准 I/O 库提供了两个函数 popen 和 pclose 函数,这两个函数实 ...

最新文章

  1. 使用动态内表——ALV输出
  2. 12123选牌漏洞_12123选牌漏洞是什么?新车牌号自编自选技巧
  3. Dynamic CRM 2013学习笔记(四十二)流程5 - 实时/同步工作流(Workflow)用法图解...
  4. Flex与.NET互操作(十六):FluorineFx + Flex视频聊天室案例开发
  5. Linux内存管理 -- smaps讲解
  6. 基于jQuery鼠标悬停上下滑动导航条
  7. Atom 编辑器安装 linter-eslint 插件,并配置使其支持 vue 文件中的 js 格式校验
  8. dll封装成activex控件_Qt编写自定义控件26-平铺背景控件
  9. 重置Winsock失败,在NSHHTTP.DLL中初始化函数InitHelperDll启动失败,错误代码为10107的解决方法
  10. CVE-2020-10148: SolarWinds 远程代码执行漏洞通告
  11. pytorch tensor 初始化_Pytorch - nn.init 参数初始化方法
  12. SR(Segment Routing)不是MPLS的优化和升级
  13. vscode 状态栏图标异常问题
  14. java中实现同步的方法
  15. 虚拟桌面分屏_无需分屏软件!让一台主机为两台显示器分屏工作的方法
  16. OpenCV Java入门三 Mat的基本操作
  17. 亚马逊ec2 实例删除_在Amazon EC2实例中的Red Hat上安装SQL Server Linux
  18. Windows 无法连接到SENS服务
  19. filebeat 收集json格式_Filebeat 采集日志实践经验记录
  20. RabbitMQ常见问题解决方案——消息丢失、重复消费、消费乱序、消息积压

热门文章

  1. python语法_嵌套
  2. C#枚举中的位运算权限分配浅谈
  3. ZOJ18th省赛 Lucky 7
  4. 这是一个什么用也没有的模板
  5. XIII Open Grodno SU Championship
  6. 分治最小割 学习总结
  7. 最简洁粗暴版的虚拟用户配置FTP
  8. TClientDataSet[28]: 读写其他格式的 XML 文件
  9. PHP新手上路(十二)
  10. JS,中文,未结束的字符常量