0 预备知识

1 不正确的调用exitValue

2不正确的调用waitFor

3 一种可接受的调用方式

4 调用认为是可执行程序的时候容易发生的错误

5 window执行的良好示例

6 不良好的重定向命令输出

7 良好的重定向输出示例

8 总结

9 问答

预备知识

Runtime类是一个与JVM运行时环境有关的Singleton类,有以下几个值得注意的地方:

0.1 Runtime.getRuntime()可以取得当前JVM的运行时环境,这也是在Java中唯一得到运行时环境的方法。

0.2 Runtime上其他大部分的方法都是实例方法,也就是说每次进行运行时调用时都要用到getRuntime方法。

0.3 Runtime中的exit方法是退出当前JVM的方法,估计也是唯一的。System类中的exit实际上也是通过调用Runtime.exit()来退出JVM的。

Java对Runtime返回值的一般规则,0代表正常退出,非0代表异常中止,这只是Java的规则,在各个操作系统中总会发生一些小的混淆。

0.4 Runtime.addShutdownHook()方法可以注册一个hook在JVM执行shutdown的过程中,方法的参数只要是一个初始化过但是没有执行的Thread实例就可以。(注意,Java中的Thread都是执行过了就不值钱的哦)

0.5说到addShutdownHook这个方法就要说一下JVM运行环境是在什么情况下shutdown或者abort的。

Shutdown:当最后一个非精灵进程退出或者收到了一个用户中断信号、用户登出、系统shutdown、Runtime的exit方法被调用时JVM会启动shutdown的过程,在这个过程开始后,他会并行启动所有登记的shutdown hook(注意是并行启动,这就需要线程安全和防止死锁)。当shutdown过程启动后,只有通过调用halt方法才能中止shutdown的过程并退出JVM。

Abort: abort退出时JVM就是停止运行但并不一定进行shutdown。这只有JVM在遇到SIGKILL信号或者windows中止进程的信号、本地方法发生类似于访问非法地址一类的内部错误时会出现。这种情况下并不能保证shutdown hook是否被执行。

0.6 Runtime.exec()方法的所有重载。这里要注意的是:

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

这个方法中cmdArray是一个执行的命令和参数的字符串数组,数组的第一个元素是要执行的命令往后依次都是命令的参数,envp中是name=value形式的环境变量设置,如果子进程要继承当前进程的环境时是null。

不正确的调用exitValue

  1. public class BadExecJavac {
  2. public static void main(String args[]) {
  3. try {
  4. Runtime rt = Runtime.getRuntime();
  5. Process proc = rt.exec("java");
  6. int exitVal = proc.exitValue();
  7. System.out.println("Process exitValue: " + exitVal);
  8. } catch (Throwable t) {
  9. t.printStackTrace();
  10. }
  11. }
  12. }

输出

  1. java.lang.IllegalThreadStateException: process has not exited
  2. at java.lang.ProcessImpl.exitValue(Native Method)
  3. at BadExecJavac.main(BadExecJavac.java:26)

错误分析:

主要问题就是错误的调用了exitValue来取得外部命令的返回值。因为exitValue方法是非阻塞的,在调用这个方法时外部命令并没有返回所以引起异常。阻塞形式的方法是waitFor,它会一直等待外部命令执行完毕,然后返回执行的结果。

当你在一个Process上调用waitFor方法时,当前线程是阻塞的,如果外部命令无法执行结束,那么你的线程就会一直阻塞下去,这种意外会影响我们程序的执行。所以在我们不能判断外部命令什么时候执行完毕而我们的程序还需要继续执行的情况下,我们就应该循环的使用exitValue来取得外部命令的返回状态,并在外部命令返回时作出相应的处理。

2不正确的调用waitFor

  1. public class BadExecJavac2 {
  2. public static void main(String args[]) {
  3. try {
  4. Runtime rt = Runtime.getRuntime();
  5. Process proc = rt.exec("javac");
  6. int exitVal = proc.waitFor();
  7. System.out.println("Process exitValue: " + exitVal);
  8. } catch (Throwable t) {
  9. t.printStackTrace();
  10. }
  11. }
  12. }

不幸的是,这个程序也无法执行完成,它没有输出但却一直悬在那里!这是为什么那?

JDK文档中的解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入和从标准输入快速的读入都有可能造成子进程的锁,甚至死锁。

JDK仅仅说明为什么问题会发生,却并没有说明这个问题怎么解决。

解决方法就是:执行完外部命令后我们要控制好Process的所有输入和输出(视情况而定),//在这个例子里边因为调用的是Javac,而他在没有参数的情况下会将提示信息输出到标准出错,所以在下面的程序中我们要对此进行处理。

一种可接受的调用方式

  1. public class MediocreExecJavac {
  2. public static void main(String args[]) {
  3. try {
  4. Runtime rt = Runtime.getRuntime();
  5. Process proc = rt.exec("javac");
  6. InputStream stderr = proc.getErrorStream();
  7. InputStreamReader isr = new InputStreamReader(stderr);
  8. BufferedReader br = new BufferedReader(isr);
  9. String line = null;
  10. System.out.println("<error></error>");
  11. while ((line = br.readLine()) != null)
  12. System.out.println(line);
  13. System.out.println("");
  14. int exitVal = proc.waitFor();
  15. System.out.println("Process exitValue: " + exitVal);
  16. } catch (Throwable t) {
  17. t.printStackTrace();
  18. }
  19. }
  20. }

输出:

  1. <error></error>
  2. Usage: javac <options></options> <source files=""></source>
  3. ...
  4. Process exitValue: 2

结果分析:

出来了结果。

为了处理好外部命令大量输出的情况,你要确保你的程序处理好外部命令所需要的输入或者输出。

调用认为是可执行程序的时候容易发生的错误

  1. public class BadExecWinDir {
  2. public static void main(String args[]) {
  3. try {
  4. Runtime rt = Runtime.getRuntime();
  5. Process proc = rt.exec("dir");
  6. InputStream stdin = proc.getInputStream();
  7. InputStreamReader isr = new InputStreamReader(stdin);
  8. BufferedReader br = new BufferedReader(isr);
  9. String line = null;
  10. System.out.println("<output></output>");
  11. while ((line = br.readLine()) != null)
  12. System.out.println(line);
  13. System.out.println("");
  14. int exitVal = proc.waitFor();
  15. System.out.println("Process exitValue: " + exitVal);
  16. } catch (Throwable t) {
  17. t.printStackTrace();
  18. }
  19. }
  20. }

输出:

  1. java.io.IOException: Cannot run program "dir": CreateProcess error=2, ...
  2. at java.lang.ProcessBuilder.start(ProcessBuilder.java:460)
  3. at java.lang.Runtime.exec(Runtime.java:593)
  4. at java.lang.Runtime.exec(Runtime.java:431)
  5. at java.lang.Runtime.exec(Runtime.java:328)
  6. at BadExecWinDir.main(BadExecWinDir.java:29)

原因分析:

因为dir命令是由windows中的解释器解释的,直接执行dir时无法找到dir.exe这个命令,所以会出现文件未找到这个2的错误。如果我们要执行这样的命令,就要先根据操作系统的不同执行不同的解释程序command.com 或者cmd.exe。

5 window执行的良好示例

  1. public class StreamGobbler extends Thread {
  2. InputStream is;
  3. String      type;
  4. StreamGobbler(InputStream is, String type) {
  5. this.is = is;
  6. this.type = type;
  7. }
  8. public void run() {
  9. try {
  10. InputStreamReader isr = new InputStreamReader(is);
  11. BufferedReader br = new BufferedReader(isr);
  12. String line = null;
  13. while ((line = br.readLine()) != null)
  14. System.out.println(type + ">" + line);
  15. } catch (IOException ioe) {
  16. ioe.printStackTrace();
  17. }
  18. }
  19. }
  1. public class GoodWindowsExec {
  2. public static void main(String args[]) {
  3. if (args.length < 1) {
  4. System.out.println("USAGE: java GoodWindowsExec <cmd></cmd>");
  5. System.exit(1);
  6. }
  7. try {
  8. String osName = System.getProperty("os.name");
  9. String[] cmd = new String[3];
  10. if (osName.equals("Windows NT")) {
  11. cmd[0] = "cmd.exe";
  12. cmd[1] = "/C";
  13. cmd[2] = args[0];
  14. } else if (osName.equals("Windows 95")) {
  15. cmd[0] = "command.com";
  16. cmd[1] = "/C";
  17. cmd[2] = args[0];
  18. }
  19. Runtime rt = Runtime.getRuntime();
  20. System.out.println("Execing " + cmd[0] + " " + cmd[1] + " " + cmd[2]);
  21. Process proc = rt.exec(cmd);
  22. // any error message?
  23. StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "ERROR");
  24. // any output?
  25. StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "OUTPUT");
  26. // kick them off
  27. errorGobbler.start();
  28. outputGobbler.start();
  29. // any error???
  30. int exitVal = proc.waitFor();
  31. System.out.println("ExitValue: " + exitVal);
  32. } catch (Throwable t) {
  33. t.printStackTrace();
  34. }
  35. }
  36. }

输出:

ExitValue: 0

原因分析:

就是cmd.exe /C +一个windows中注册了后缀的文档名,windows会自动地调用相关的程序来打开这个文档。

不要假设你执行的程序是可执行的程序,要清楚自己的程序是单独可执行的还是被解释的, 这里还有一点,就是得到process的输出的方式是getInputStream,这是因为我们要从Java 程序的角度来看,外部程序的输出对于Java来说就是输入,反之亦然。

不良好的重定向命令输出

错误的认为exec方法会接受所有你在命令行或者Shell中输入并接受的字符串。这些错误主要出现在命令作为参数的情况下,程序员错误的将所有命令行中可以输入的参数命令加入到exec中。下面的例子中就是一个程序员想重定向一个命令的输出。

  1. public class BadWinRedirect {
  2. public static void main(String args[]) {
  3. try {
  4. Runtime rt = Runtime.getRuntime();
  5. Process proc = rt.exec("java jecho 'Hello World' > test.txt");
  6. // any error message?
  7. StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "ERROR");
  8. // any output?
  9. StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "OUTPUT");
  10. // kick them off
  11. errorGobbler.start();
  12. outputGobbler.start();
  13. // any error???
  14. int exitVal = proc.waitFor();
  15. System.out.println("ExitValue: " + exitVal);
  16. } catch (Throwable t) {
  17. t.printStackTrace();
  18. }
  19. }
  20. }

程序员的本意是将Hello World这个输入重订向到一个文本文件中,但是这个文件并没有生成,jecho仅仅是将命令行中的参数输出到标准输出中,用户觉得可以像dos中重定向一样将输出重定向到一个文件中,但这并不能实现,用户错误的将exec认为是一个shell解释器,但它并不是,如果你想将一个程序的输出重定向到其他的程序中,你必须用程序来实现他。可用java.io中的包。

良好的重定向输出示例

  1. public class StreamGobbler extends Thread {
  2. InputStream is;
  3. String      type;
  4. OutputStream os;
  5. StreamGobbler(InputStream is, String type) {
  6. this(is, type, null);
  7. }
  8. StreamGobbler(InputStream is, String type, OutputStream redirect) {
  9. this.is = is;
  10. this.type = type;
  11. this.os = redirect;
  12. }
  13. public void run() {
  14. try {
  15. PrintWriter pw = null;
  16. if (os != null)
  17. pw = new PrintWriter(os);
  18. InputStreamReader isr = new InputStreamReader(is);
  19. BufferedReader br = new BufferedReader(isr);
  20. String line = null;
  21. while ((line = br.readLine()) != null) {
  22. if (pw != null)
  23. pw.println(line);
  24. System.out.println(type + ">" + line);
  25. }
  26. if (pw != null)
  27. pw.flush();
  28. } catch (IOException ioe) {
  29. ioe.printStackTrace();
  30. }
  31. }
  32. }
  1. public class GoodWinRedirect {
  2. public static void main(String args[]) {
  3. args = new String[1];
  4. args[0]="g:\\out.txt";
  5. if (args.length < 1) {
  6. System.out.println("USAGE java GoodWinRedirect <outputfile></outputfile>");
  7. System.exit(1);
  8. }
  9. try {
  10. FileOutputStream fos = new FileOutputStream(args[0]);
  11. Runtime rt = Runtime.getRuntime();
  12. Process proc = rt.exec("java jecho 'Hello World'");
  13. // any error message?
  14. StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "ERROR");
  15. // any output?
  16. StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "OUTPUT", fos);
  17. // kick them off
  18. errorGobbler.start();
  19. outputGobbler.start();
  20. // any error???
  21. int exitVal = proc.waitFor();
  22. System.out.println("ExitValue: " + exitVal);
  23. fos.flush();
  24. fos.close();
  25. } catch (Throwable t) {
  26. t.printStackTrace();
  27. }
  28. }
  29. }

总结

总结了几条规则,防止我们在进行Runtime.exec()调用时出现错误。

  • 在一个外部进程执行完之前你不能得到他的退出状态
  • 在你的外部程序开始执行的时候你必须马上控制输入、输出、出错这些流。
  • 你必须用Runtime.exec()去执行程序
  • 你不能象命令行一样使用Runtime.exec()。

问答

问:为什么Runtime.exec("ls")没有任何输出?

答:调用Runtime.exec方法将产生一个本地的进程,并返回一个Process子类的实例,该实例可用于控制进程或取得进程的相关信息。

由于调用Runtime.exec方法所创建的子进程没有自己的终端或控制台,因此该子进程的标准IO(如stdin,stdou,stderr)都通过Process.getOutputStream(),Process.getInputStream(),Process.getErrorStream()方法重定向给它的父进程了。用户需要用这些stream来向子进程输入数据或获取子进程的输出。所以正确执行Runtime.exec("ls")的例程如下:

  1. try {
  2. Process process = Runtime.getRuntime().exec(command);
  3. InputStreamReader ir = new InputStreamReader(process.getInputStream());
  4. LineNumberReader input = new LineNumberReader(ir);
  5. String line;
  6. while ((line = input.readLine()) != null)
  7. System.out.println(line);
  8. } catch (java.io.IOException e) {
  9. System.err.println("IOException " + e.getMessage());
  10. }

(原文地址:http://jiangshuiy.iteye.com/blog/1674235)

[转]Java中Runtime.exec的一些事的更多相关文章

  1. Java中RunTime类介绍

    Runtime 类代表着Java程序的运行时环境,每个Java程序都有一个Runtime实例,该类会被自动创建,我们可以通过Runtime.getRuntime() 方法来获取当前程序的Runtime ...

  2. java中Runtime类

    一.概述      Runtime类封装了运行时的环境.每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接.      一般不能实例化一个Runtime对象, ...

  3. java中Runtime类和Process类的简单介绍

    在java.lang包当中定义了一个Runtime类,在java中对于Runtime类的定义如下: Java code public class Runtime extends Object 每个 J ...

  4. java使用Runtime.exec()运行windwos dos或linux shell命令

    使用Runtime.exec()运行windwos dos或linux shell命令,按实际情况具体测试     实例代码: package com.bookoo.test.command; imp ...

  5. Java运行系统命令并获取值(Process java.lang.Runtime.exec(String[] cmdarray, String[] envp, File dir)

    package test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; ...

  6. java中Runtime类详细介绍

    Runtime类描述了虚拟机一些信息.该类采用了单例设计模式,可以通过静态方法 getRuntime()获取Runtime类实例.下面演示了获取虚拟机的内存信息: package Main; publ ...

  7. java.lang.Runtime.exec() Payload Workarounds

    由于Runtime.getRuntime().exec()中不能使用管道符等bash需要的方法,我们需要用进行一次编码 编码工具: 地址: http://jackson.thuraisamy.me/r ...

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

    最近要实现一个Java调用一个复杂shell命令实现数据同步,该命令有管道重定向的语句,结果硬是执行不成功,而且也没异常报出.经过一段时间的折腾终于解决了此问题,权当做备忘记录下来(重点在红色框中的“ ...

  9. Java中RunTime.getRunTime().addShutdownHook用法

    今天在阅读Tomcat源码的时候,catalina这个类中使用了下边的代码,不是很了解,所以google了一下,然后测试下方法,Tomcat中的相关代码如下: Runtime.getRuntime() ...

随机推荐

  1. JS实现拖动(2)

    getBoundingClientRect() 来获取页面元素的位置 document.documentElement.getBoundingClientRect 该方法返回一个对象,从而获得页面中某 ...

  2. 微信小程序 - template和include详细描述

    演示 index.wxml <!-- 内部模板 --> <template name="all"> {{a}} {{b}} </template> ...

  3. MySql 比Replace Into更适合的用法,外加SqlServer的方式。

    Mysql: INSERT INTO `his_examine_result` (Mid,His_Examine_Mid, His_File_Mid, ResultType, His_Employee ...

  4. C#(WPF和WinForm)在普通类中调用到主线程的方法,SynchronizationContext的用法。

    一.SynchronizationContext类用法: 1.对于WindowsFrom应用程序,如果想在某个类中,不方便使用到控件的Invoke方法时,可以使用WindowsBase.dll下的Sy ...

  5. web ide

    https://www.jianshu.com/p/339dff3da1fa https://www.eclipse.org/che/ https://github.com/Coding/WebIDE ...

  6. 前端安全系列(一):如何防止XSS攻击?

    原文:https://my.oschina.net/meituantech/blog/2218539 前端安全 随着互联网的高速发展,信息安全问题已经成为企业最为关注的焦点之一,而前端又是引发企业安全 ...

  7. SpringBoot引入freemaker前端模板

    1.引入freeMarker的依赖包 <!-- 引入freeMarker的依赖包. --> <dependency> <groupId>org.springfram ...

  8. OdiSendMail

    在Package中使用ODI自带的发送邮件OdiSendMail,生成的场景迁移到正式环境中,提示 javax.mail.AuthenticationFailedException: failed t ...

  9. 转 PHP5+APACHE2.2配置

    初学php,配置起来老出问题,找了篇不错的帖子,一试就通过了,所以就顺带着转了过来. 不过在我安装phpMyAdmin的时候还是发现这篇文章的一个问题,就是php.ini如果放在system32下,启 ...

  10. Spring Cloud环境搭建: Eureka Server

    项目目录结构, 总共三个文件 ├── pom.xml └── src ├── main │   ├── java │   │   └── com │   │   └── rockbb │   │   ...