一、快速入门
1.1 Runtime 介绍
Java Runtime 类是 Java 标准库中的关键类之一,它提供了对当前Java虚拟机实例的访问和控制,允许程序动态地修改和管理运行时环境。每个Java应用程序都有一个Runtime类实例,使得程序能够与其运行的环境相连接。
Runtime类所在包为java.lang包,因此在使用的时候不需要进行导包。并且Runtime类被public修饰了,因此该类是可以被继承的。
1.2 常用方法
程序中一般不能显式的主动实例化一个Runtime实例,而是通过Runtime.getRuntime()
来获取当前程序的Runtime实例。通过getRuntime()
获取到当前程序的运行环境之后,就可以做很多事了,比如说调用运行环境,执行一些cmd任务,或者外部的脚本等。由于Runtime类封装了虚拟机进程,因此,在程序中通常会通过该类的实例对象来获取当前虚拟机的相关信息。其常用方法和功能如下表所示:
1.2.1 基本方法
方法 | 描述 |
---|---|
static Runtime getRuntime() | 获取当前Java应用程序相关的运行时对象 |
1.2.2 行为控制类
方法 | 描述 |
---|---|
void addShutdownHook(Thread hook) | 注册新的虚拟机来关闭挂钩 |
boolean removeShutdownHook(Thread hook) | 移除一个虚拟机关机任务,与addShutdownHook作用相反 |
void exit(int status) | 通过启动虚拟机的关闭序列,终止当前正在运行的Java虚拟机 |
void gc() | 调用该方法可以触发垃圾回收器的执行,以回收不再使用的对象占据的内存空间<br>System.gc()就是调用的Runtime的gc()函数 |
void halt(int status) | 强行终止目前正在运行的Java虚拟机 |
1.2.3 系统信息类
方法 | 说明 |
---|---|
int availableProcessors() | 向 Java 虚拟机返回可用处理器的数目 |
long freeMemory() | 返回当前Java虚拟机中的空闲内存量 |
long maxMemory() | 返回当前Java虚拟机可以使用的最大内存量,即堆的最大容量 |
long totalMemory() | 返回当前Java虚拟机当前已经使用的总内存量 |
> 注意:Runtime 类的 freeMemory、totalMemory、maxMemory三个方法反映的都是 Java 这个进程的内存情况,跟操作系统的内存根本没有关系。
1.2.4 exec
重载的exec()
函数,可以执行指定的参数来达到执行cmd命令的目的。
方法 | 说明 |
---|---|
exec(String command) | 用于在操作系统中执行指定的命令,它创建一个新的进程并在其中执行给定的命令 |
exec(String[] cmdarray) | 在单独的进程中执行指定命令和变量 |
exec(String[] cmdarray,String[] envp) | 在指定环境的独立进程中执行指定命令和变量 |
exec(String[] cmdarray,String[] envp,File dir) | 在指定环境和工作目录的独立进程中执行指定的命令和变量 |
exec(String command,String[] envp) | 在指定环境的单独进程中执行指定的字符串命令 |
exec(String command,String[] envp,File dir) | 在有指定环境和工作目录的独立进程中执行指定的字符串命令 |
1.2.5 其他方法
方法 | 说明 |
---|---|
load(String filename) | 加载作为动态库的指定文件名 |
loadLibrary(String libname) | 加载具有指定库名的动态库 |
runFinalization() | 运行挂起finalization的所有对象的终止方法 |
traceInstructions(on) | 启用/禁用指令跟踪 |
traceMethodCalls(on) | 启用/禁用方法调用跟踪 |
1.2.6 注意事项
这些方法允许Java应用程序与其运行时环境进行交互,获取内存使用情况、执行系统命令等操作。需要注意的是,在使用exec()
方法执行系统命令时,可能需要处理输入流和输出流,以便与子进程进行通信,并正确处理可能的异常情况。在runtime执行大点的命令中,输入流和错误流会不断有流进入存储在JVM的缓冲区中,如果缓冲区的流不被读取被填满时,就会造成runtime的阻塞。所以在进行比如:大文件复制等的操作时,我们还需要不断的去读取JVM中的缓冲区的流,来防止Runtime的死锁阻塞。
二、基本使用
2.1 获取当前虚拟机信息
Runtime类可以获取当前Java虚拟机的处理器的个数、空闲内存量、最大可用内存量和内存总量的信息。
public static void main(String[] args) {
// 获取当前系统的运行环境对象
Runtime rt = Runtime.getRuntime();
System.out.println("处理器的个数: " + rt.availableProcessors() + "个");
System.out.println("空闲内存数量: " + rt.freeMemory() / 1024 / 1024 + "M");
System.out.println("最大可用内存数量: " + rt.maxMemory() / 1024 / 1024 + "M");
System.out.println("虚拟机中内存总量: " + rt.totalMemory() / 1024 / 1024 + "M");
}
> 由于每个人的机器配置不同,该示例的打印结果可能不同,另外空闲内存数、可用最大内存数和内存总量都是以字节为单位计算的,上述运行结果已经将字节换算成了兆(M)。
2.2 操作系统进程
Runtime类中提供了一个exec()方法,该方法用于执行指定的字符串命令,从而实现和在命令行窗口中输入命令同样的效果。该方法返回一个代表进程的Process
对象,可以通过该对象获取进程的输入流、输出流和出错流,并与进程进行交互。例如,通过运行“notepad.exe”命令打开一个Windows自带的记事本程序。程序运行后,会在Windows系统中产生一个新的进程notepad.exe,可以通过任务管理器进行观察。
public static void main(String[] args) throws IOException {
// 创建Runtime实例对象
Runtime rt = Runtime.getRuntime();
// 调用exec()方法
rt.exec("notepad.exe");
}
Runtime 是 Java 提供的一个启动子进程来执行命令的方式,它提供了以下六个重载的 exec
方法,用于单独启动一个子进程来执行命令或调用程序。每一个方法最终都返回一个 Process
对象表示一个进程,从该对象中能够获取进程执行的退出状态码、标准输出流和标准错误流,进而获取进程执行的输出内容。
public Process exec(String command) 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[]) throws IOException
public Process exec(String[] cmdarray, String[] envp) throws IOException
public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException
其中,前五个方法都是间接调用最后一个方法,该方法有 3 个参数:
参数 | 说明 |
---|---|
cmdarray | 命令字符串数组 |
envp | 字符串数组(可以为空),表示命令执行过程中设置的环境变量 |
dir | 一个File对象(可以为空),表示命令执行的工作目录。<br>如果命令中有使用到类似于./ 的相对路径,则该相对路径就是基于dir的 |
这里需要特殊说明一下,单字符串命令和命令数组的区别,总结如下:
- 如果执行的命令的参数中包含空格一定要使用
exec(String[] cmdarray, String[] envp, File dir)
,否则将导致命令执行失败。 - 如果执行的命令的参数中没有空格不会导致参数分裂,则两个方法都一样。
2.3 Process对象
查阅API文档会发现,Runtime类的exec()方法返回一个Process对象,该对象就是exec()所生成的新进程,通过该对象可以对产生的新进程进行管理,如关闭此进程只需任务管理器中终止记事本进程的命令即可。具有代码如下所示:
public static void main(String[] args) {
try {
Process process = Runtime.getRuntime().exec("notepad.exe");
// 休眠3秒
Thread.sleep(3000);
// 执行任务管理器中终止记事本进程的命令
Runtime.getRuntime().exec("taskkill /F /IM notepad.exe");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Process的几种方法
方法 | 说明 |
---|---|
destroy() | 杀掉子进程 |
exitValue() | 返回子进程的出口值,值0表示正常终止 |
getErrorStream() | 获取子进程的错误流 |
getInputStream() | 获取子进程的输入流 |
getOutputStream() | 获取子进程的输出流 |
waitFor() | 导致当前线程等待,如有必要,一直要等到由该Process对象表示的进程已经终止。<br>如果已终止该子进程,此方法立即返回。<br>如果没有终止该子进程,调用的线程将被阻塞,直到退出子进程,根据惯例,0表示正常终止 |
三、业务场景实战
3.1 执行外部脚本
下面是一个演示如何使用 Runtime
类来执行外部脚本的 Java 实例程序:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ExecuteScriptExample {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
try {
// 使用 runtime.exec() 方法执行外部脚本
Process process = runtime.exec("python script.py");
// 获取外部脚本的输入流,并创建 BufferedReader 对象用于读取输出结果
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
// 处理外部脚本输出的每一行
System.out.println(line);
}
// 等待外部进程结束并获取退出值
int exitCode = process.waitFor();
if (exitCode == 0) {
// 外部脚本执行成功
System.out.println("External script executed successfully.");
} else {
// 外部脚本执行失败
System.err.println("External script execution failed. Exit code: " + exitCode);
}
} catch (IOException | InterruptedException e) {
// 处理异常
e.printStackTrace();
}
}
}
这个实例展示了如何在 Java 中使用 Runtime
类来执行外部脚本,并处理输出结果和异常情况。注意,使用 runtime.exec()
执行外部命令需要谨慎处理,特别是针对从用户输入或不受信任的源代码中派生的命令。建议对脚本及其参数进行适当的验证和限制,以确保安全性。
3.2 动态加载类
下面是一个演示如何使用 Runtime
类结合反射机制来动态加载类的Java实例程序:
import java.lang.reflect.Method;
public class DynamicClassLoadingExample {
public static void main(String[] args) {
try {
// 创建Runtime对象
Runtime runtime = Runtime.getRuntime();
// 加载需要动态加载的类的全限定名
String className = "com.example.MyClass";
// 使用ClassLoader动态加载类
Class<!--?--> dynamicClass = runtime.getClass().getClassLoader().loadClass(className);
// 创建类的实例
Object instance = dynamicClass.newInstance();
// 调用类中的方法
Method method = dynamicClass.getDeclaredMethod("doSomething");
method.invoke(instance);
} catch (ClassNotFoundException e) {
// 处理类未找到异常
e.printStackTrace();
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
// 处理其他异常
e.printStackTrace();
}
}
}
这个实例演示了如何使用 Runtime
类动态加载类,并调用其中的方法。需要注意,动态加载类在某些场景下是有用的,但也应谨慎使用。合理地处理异常情况以及对类名、方法等进行适当的验证和限制是非常重要的。
四、小结
在学习面向对象概念时曾经强调过一个概念,一个类中至少会存在一个构造方法, 如果本类没有定义任何构造方法,那么会自动生成一个无参的构造方法。 但是当打开 Runtime类时会发现一个问题,在这个类中并没有构造方法的定义说明,可是这个类的构造方法却是真实存在的,因为其在声明时对构造方法进行了封装 所以Runtime类是一个典型的单例设计模式。
Java Runtime 类作为Java虚拟机的实例,对于动态地控制运行时环境至关重要。通过提供各种功能和方法,例如执行外部命令、管理内存和垃圾回收,以及动态加载和卸载类,Runtime 类使得Java程序能够更加灵活地与底层系统进行交互。然而,开发人员需要注意安全性、性能优化及平台依赖性等方面,以确保代码的可靠性和效率。熟练使用 Runtime 类,并根据具体需求进行合理的优化,将有助于实现更强大和高效的Java应用程序。