【知识回顾】Java常用类库-Java Runtime

原创
2023/09/30 12:21
阅读数 36

一、快速入门

1.1 Runtime 介绍

  Java Runtime 类是 Java 标准库中的关键类之一,它提供了对当前Java虚拟机实例的访问和控制,允许程序动态地修改和管理运行时环境。每个Java应用程序都有一个Runtime类实例,使得程序能够与其运行的环境相连接。
  Runtime类所在包为java.lang包,因此在使用的时候不需要进行导包。并且Runtime类被public修饰了,因此该类是可以被继承的。

image

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应用程序。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部