java程序可以调用Jvm外部的一些应用程序来完成一些功能.目前基于JDK1.8来做实验~
目前java有如下两种方式来调用其它程序, Runtime和ProcessBulider提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。(这些都能设置,就是帮你找到外部可执行文件的位置与启动变量,具体怎么执行,可以查看Runtime和ProcessBuilder的构造方法,里面有设置)
但是这两种方法都会返回一个用于管理操作系统进程的Process对象。另外两者的启动差别不是很大.最终生成的Process子进程是独立于Jvm之外的进程,就算没有对象应用指向它,它也不会被GC关闭或者释放.
在用Runtime.getRuntime().exec()或ProcessBuilder(array).start()创建子进程Process之后,一定要及时取走子进程的输出信息和错误信息,否则输出信息流和错误信息流很可能因为信息太多导致被填满,最终导致子进程阻塞住,然后执行不下去。
这么来说其实重要的是Process的使用.
- Runtime的exec()方法,并返回一个Process对象
- ProcessBuilder的start()方法,并返回一个Process对象
Runtime类是Java程序的运行时环境。不能new出一个Runtime对象,只能通过getRuntime()方法获取当前Runtime运行时对象的引用。然后可以调用Runtime的方法查看和修改Java虚拟机的状态。
Runtime的主要的方法如下所示:
- exec方法接收一个命令然后执行,通过该方法可以执行和cmd中用法一样命令。比如,Runtime.getRuntime().exec(“ls”),就和cmd中执行ls效果一样了。
- freeMemory()可以查看当前虚拟机内存中空闲内存还有多少。
- totalMemory()可以查看当前虚拟机使用的总内存大小。
- maxMemory()可以查看JVM的最终可以使用的最大内存是多少。
- availableProcessors()可以查看本机有多少处理器,即本机处理器是多少核。
- exit(int)方法可以退出当前Java程序的运行,System.exit(int)方法就是调用了Runtime.exit(int)方法来退出运行的。
- 示例,
ProcessBuilder是java 5.0引入的,start()方法返回Process的一个实例.所以被推荐使用~~
Runtime和ProcessBuilder的不同点就是,启动子进程时的命令形式不同,Runtime.getRuntime.exec()可以把命令和参数写在一个String中,用空格分开,ProcessBuilder则是构造函数的参数中,传递一个由命令和参数组成的list或数组。
ProcessBuilder的构造方法接收一个命令参数的数组形式,其中,第一个元素代表要执行的系统命令,后面的元素代表要传给该命令的参数。
ProcessBuilder 没有Runtime那样直接获取环境参数的方法,只有一个enviroment()返回一个Map~.
Process
看下Api怎么说的,从中分理处一些关键信息;
java.lang
类 Process
java.lang.Object
java.lang.Process
public abstract class Processextends Object
ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,该
实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检
查进程的退出状态以及销毁(杀掉)进程的方法。
创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,
Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的
所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、
getInputStream() 和 getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得
从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或
输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。 cuiyaonan2000@163.com
当没有 Process 对象的更多引用时,不是删掉子进程,而是继续异步执行子进程。
对于带有 Process 对象的 Java 进程,没有必要异步或并发执行由 Process 对象表示的进程。
关键点:
- Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。
-
当没有 Process 对象的更多引用时,不是删掉子进程,而是继续异步执行子进程。
-
对于带有 Process 对象的 Java 进程,没有必要异步或并发执行由 Process 对象表示的进程。(毛意思:就是单线程么,不需要在建子进程来分担任务量么~~~~~~)
杀掉该Process对象代表的进程。(还有个以强杀方法.是不是类似于kill -9 ~~~)
exitValue返回该Process对象代表的进程的出口值,值0表示正常退出,非0非正常。关于该方法,应该是返回System.exit(int)方法中的参数。
waitFor返回该Process对象代表的进程的出口值,值0表示正常退出,非0非正常。
该方法很类似exitValue方法,但是他们有个取别很大的地方,那就是exitValue方法会直接返回一个值,但是当前程序有可能还在运行中,所以该值不一定是正确的,而本方法会一直等待Process对象代表的进程退出后才返回值,而且调用本方法的进程将会一直堵塞等待返回值。建议使用本方法!
关于获取外部进程的日志的问题getErrorStream:是获取的错误日志(即error级别的日志)
getInputStream:是获取外部进程的一般性日志如(info,warn,debug)
平时我们的一份完整的日志应该是 info+error的日志 才算完整.所以应该把这2个流合并到一起展示,才是完整的.方法如下
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException, InterruptedException {
Runtime rt = Runtime.getRuntime();//获得Runtime对象
String arr[] = {"CLASSPATH=D://","Path=C:\\Program Files\\Java\\jdk1.8.0_131\\bin"};//执行exec时的环境变量
//exec方法第一个参数是执行的命令,第二个参数是环境变量,第三个参数是工作目录
Process pr = rt.exec("cmd /c javac a.java && java a", arr, new File("D://"));
//获取输出流并转换成缓冲区
BufferedWriter bout = new BufferedWriter(new OutputStreamWriter(pr.getOutputStream()));
bout.write("1 2");//输出数据
bout.close();//关闭流
//SequenceInputStream是一个串联流,能够把两个流结合起来,通过该对象就可以将
//getInputStream方法和getErrorStream方法获取到的流一起进行查看了,当然也可以单独操作
SequenceInputStream sis = new SequenceInputStream(pr.getInputStream(), pr.getErrorStream());
InputStreamReader inst = new InputStreamReader(sis, "GBK");//设置编码格式并转换为输入流
BufferedReader br = new BufferedReader(inst);//输入流缓冲区
String res = null;
StringBuilder sb = new StringBuilder();
while ((res = br.readLine()) != null) {//循环读取缓冲区中的数据
sb.append(res+"\n");
}
br.close();
pr.waitFor();
pr.destroy();
System.out.print(sb);//输出获取的数据
}
}