- 概述
- Runtime 类
- Process类
- 实例
概述 Java应用程序主要是通过Runtime和Process两个类来执行cmd命令。
Runtime.exec方法创建本机进程并返回Process子类的实例,该实例可用于控制进程并获取有关它的信息。
Process类提供了从进程执行输入,执行输出到进程,等待进程完成,检查进程的退出状态以及销毁(杀死)进程的方法。
具体有哪些方法请查看相关文档:https://docs.oracle.com/javase/8/docs/api/
Runtime 类 ##### 1、Runtime 类的主要作用 在每一个JVM进程里面都会存在有一个Runtime类的对象,这个类的主要功能是取得一些与运行时有关的环境属性,或者创建新的进程。
在Runtime类定义的时候,它的构造方法已经被私有化了(单例设计模式的应用),以此保证,在整个运行过程中,只有唯一一个Runtime类的对象。所以在Runtime类里面提供有一个static方法,取得Runtime类实例对象
2、Runtime 类方法public class Runtime {
private static Runtime currentRuntime = new Runtime();
//返回与当前Java应用程序关联的运行时对象。 类Runtime的大多数方法都是实例方法,必须根据当前运行时对象进行调用。
public static Runtime getRuntime()
public Process exec(String command)
public Process exec(String command, String[] envp)
public Process exec(String command, String[] envp, File dir)
public Process exec(String[] cmdarray, String[] envp, File dir)
// 返回Java虚拟机中的空闲内存量,以字节为单位
public native long freeMemory()
//返回Java虚拟机试图使用的最大内存量
public native long maxMemory()
//返回Java虚拟机中的内存总量
public native long totalMemory()
// 返回最大可用内存空间
public long maxMemory();
// 返回空余内存空间
public long freeMemory();
// 手动实现JVM的gc机制
public void gc()
}
Process类 ##### 1、概述 Process 类提供了从进程执行输入,执行输出到进程,等待进程完成,检查进程的退出状态以及销毁(杀死)进程的方法。
每个进程生成器ProcessBuilder对象管理这些进程属性:
- 命令:是一个字符串列表,它表示要调用的可执行外部程序文件及其参数(如果有)。
- 环境:是从变量 到值 的依赖于系统的映射。
- 工作目录:默认值是当前进程的当前工作目录,通常根据系统属性 user.dir 来命名。
- redirectErrorStream 属性:子进程的标准输出和错误输出是否被发送给发送给两个独立的流(Process.getInputStream() 和 Process.getErrorStream()),默认false发送。
ProcessBuilder.start()和 Runtime.exec()方法创建操作系统进程并返回 Process子类的实例,该实例可用于控制进程并获取有关它的信息。 Runtime.exec() 可接受一个单独的字符串,这个字符串是通过空格来分隔可执行命令程序和参数的;也可以接受字符串数组参数/list。ProcessBuilder.start() 只支持字符串数组参数。
Runtime.exec最终是通过调用ProcessBuilder来真正执行操作的:
// ProcessBuilder 第一个参数必须是可执行程序,可以添加参数使用{"cmd", "/c"} 或 {"/bin/bash", "-c"}
public Process exec(String[] cmdarray, String[] envp, File dir)
throws IOException {
// 在 directory() 指定的工作目录中,利用 environment() 指定的进程环境,新进程将调用由 command() 给出的命令和参数。
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
ProcessBuilder.start() 现在是使用已修改环境启动进程的首选方法。新子进程的工作目录由dir指定。如果dir为null,则子进程将继承当前进程的当前工作目录。如果存在安全管理器,则调用其checkExec() 方法,并将数组cmdarray[] 的第一个组件作为其参数。这可能会导致抛出SecurityException。
启动操作系统进程高度依赖于程序运行所在的操作系统。在许多可能出现如下错误:
- 找不到操作系统程序文件 - 访问程序文件被拒绝 - 工作目录不存在
在这种情况下,程序将会抛出异常。异常的确切性质取决于操作系统,但它始终是IOException的子类。
创建进程的方法可能不适用于某些本机平台上的特殊进程,例如本机窗口进程,守护程序进程,Microsoft Windows上的Win16 / DOS进程或shell脚本。
创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin,stdout,stderr)操作都将通过三个流(getOutputStream(),getInputStream(),getErrorStream()) 重定向到父进程。父进程使用这些流向子进程提供输入并从子进程获取输出。由于某些本机平台仅为标准输入和输出流提供有限的缓冲区大小,因此无法及时写入输入流或读取子进程的输出流可能导致子进程阻塞甚至死锁。如果需要,还可以使用ProcessBuilder类的方法重定向子进程I/O。
当没有对Process对象的更多引用时,子进程不会被终止,而是子进程继续异步执行。不要求Process对象表示的进程相对于拥有Process对象的Java进程异步或并发执行。
2、Process类方法public abstract class Process {
public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException
}
3、安全风险
-
描述
java.lang.Process 对象描述进程可能需要通过其输入流对其提供输入,并且其输出流、错误流或两者同时会产生输出。不正确地处理这些外部程序可能会导致一些意外的异常、DoS,及其他安全问题。
一个进程如果试图从一个空的输入流中读取输入,则会一直阻塞,直到为其提供输入。因此,在调用这样的进程时,必须为其提供输入。
一个外部进程的输出可能会耗尽该进程输出流与错误流的缓冲区。当发生这种情况时,Java 程序可能会阻塞外部进程,同时阻碍Java程序与外部程序的继续运行。因此,在运行一个外部进程时,如果此进程往其输出流发送任何数据,则必须将其输出流清空。类似的,如果进程会往其错误流发送数据,其错误流也必须被清空。 -
处理建议
对于那些从来不会读取其输入流的进程,不对其提供输入非但无害,且还有益。而对于那些从来不会发送数据到其输出流或者错误流的进程,不对其输出流或者错误流进行清空同样是有益无害的。因此,只要能够保证进程不会使用这些流,那么在程序中可以忽略其输入流、输出流、以及错误流。
- 原因
有些本机平台仅针对标准输入和输出流提供有限的=缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败(如不断发送数据),而主进程调用Process.waitfor后已挂起,则可能导致子进程阻塞,进程间相互等待甚至产生死锁。
// Do not let external processes block on I|O streams
// 场景一: 使用java.lang.ProcessBuilder.redirectErrorStream(boolean redirectErrorStream)方法即可清空流
ProcessBuilder builder = new ProcessBuilder(cmds);
builder.redirectErrorStream(true);
try {
process = builder.start();
} catch (IOException e) {
e.pringtStackTrace();
}
// 场景二:当出现IOException异常时不应该将IOException异常throws,使用try/catch对IOException单独捕获
Process process = null;
try {
process = builder.start();
} catch (IOException e) {
e.pringtStackTrace();
}
String handleMessage = "";
BufferedReader bufferedReader = new BufferedSReader(new InputStreamReader(process.getInputStream, StandardCharesets.UTF_8));
try {
while ((handleMessage = bufferedReader.readLine()) != null) {
System.out.println(handleMessage);
}
} catch (IOException e) {
e.pringtStackTrace();
}
try {
bufferedReader.close();
} catch (IOException e) {
e.pringtStackTrace();
}
Process process = Runtime.getRuntime().exec(str);
// 记录进程缓存错误信息
final StringBuffer errorLog = new StringBuffer();
final InputStream errorStream = process.getErrorStream();
final InputStream inputStream = process.getInputStream();
// 处理InputStream的线程
new Thread() {
@Override
public void run() {
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
String line = null;
try {
// 消费掉缓存中的数据
while ((line = in.readLine()) != null && !errorLog.toString().contains("ERROR")) {
if (line != null) {
errorLog.append(line);
}
}
} catch (IOException e) {
// public RuntimeException(String message, Throwable cause)
throw new RuntimeException("[shell exec error]:" + errorLog, e);
} finally {
try {
inputStream.close();;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
// 处理errorStream的线程
new Thread() {
@Override
public void run() {
BufferedReader err = new BufferedReader(new InputStreamReader(errorStream));
String line = null;
try {
// 消费掉缓存中的数据
while ((line = err.readLine()) != null && !errorLog.toString().contains("ERROR")) {
if (line != null) {
errorLog.append(line);
}
}
} catch (IOException e) {
throw new RuntimeException("[shell exec error]:" + errorLog, e);
} finally {
try {
errorStream.close();;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
logger.info("等待shell脚本执行完成");
Thread.sleep(1000);
// 异常终止
if (errorLog != null && errorLog.length() > 0 && errorLog.toString().contains("ERROR")) {
dispatchLogger.error("[shell exec error]:" + errorLog);
throw new RuntimeException("[shell exec error]:" + errorLog);
}
// 等待shell脚本执行完成
process.waitFor();



