科普文:一文搞懂jvm实战(一)Runtime实时监控jvm

发布于:2024-07-06 ⋅ 阅读:(38) ⋅ 点赞:(0)

概叙

        Java Runtime 类是 Java 标准库中的关键类之一。它提供了对当前Java虚拟机(JVM)实例的访问和控制,允许程序动态地修改和管理运行时环境。

        Java Runtime 是Java虚拟机(JVM)的一个实例,代表了正在执行Java应用程序的运行时环境。Runtime 类封装了访问底层系统和控制JVM行为的方法,使得程序能够与运行时环境进行交互。

优势和缺点


优点:

  •         控制整个JVM:        Runtime 类提供了许多方法来管理JVM的行为,如内存管理、垃圾回收等。  这使得开发人员可以根据自己的需求调整JVM的配置,并最大限度地使用系统资源;
  •         外部交互能力:通过 exec 方法,Runtime 类可以执行外部命令,允许程序与操作系统进行交互。  这样,Java程序就能够调用其他系统工具或执行外部脚本;
  •         动态类加载:Runtime 类支持在运行时动态加载和卸载类,从而提供了更大的灵活性和扩展性。


缺点:

  •         安全性问题:某些 Runtime 类的方法可能存在安全风险,尤其是在处理用户输入时。开发人员应谨慎使用这些方法,并进行适当的输入验证和安全性检查;
  •         平台依赖性:Runtime 类涉及底层系统资源和行为,因此其表现可能依赖于特定的操作系统和JVM实现。在不同平台上的行为可能会有所差异。

 Runtime类

        Runtime类描述的是一种运行时,在Java程序执行过程中,所有的java程序都一定要运行在JVM(虚拟机)的进程中有了JVM进程,就需要一种类型可以描述当前进程的相关环境以及与之相关的处理操作.

        即Java设计出了Runtime类​​​​​​​每个JVM的进程中都会自动包含有一个Runtime类的实例化对象,打开Runtime类之后就可以看见相关文档。

[查看文档链接](java.base (Java SE 11 & JDK 11 ) (oracle.com))

        每一个JavaDos文档应该按照"成员"、"构造"、"方法"的形式进行类的结构展示,而Runtime类的文档直接给出的是"方法"摘要

        原因:Runtime类中的构造方法被隐藏了,是私有属性的,因此可知Runtime类应该属于单例设计模式如下:

(注意:若属于多例设计模式,在新的开发版本中一定会使用枚举来描述)

private Runtime(){}

Runtime类设置为单例设计模式

        将Runtime类设置为单例设计模式,主要是因为每个JVM进程只需要提供一个Runtime类的实例化对象,所有和JVM有关的信息实际上都在Runtime类中提供了操作。

        Runtime run= Runtime.getRuntime();    // 取得Runtime 类的实例化对象

        图解如下:

 Runtime 类常用方法

        虽然Runtime类是单例设计模式,但从本质上来讲单例设计模式都会在内部提供一个获取本类实例化对象的static方法,这个方法在Runtime类中也存在;Runtime类常用的处理方法如下:

方法名称 类型 描述
public static Runtime getRuntime() 普通 获取Runtime类的实例化对象
public static exec(String command) throws IOException 普通 开启新的子进程
public long maxMemory() 普通 最大可用内存
public long totalMemor() 普通 可用内存
public long freeMemory() 普通 空余内存
public void gc() 普通 手工执行GC

        通过以上的方法列表可知,在Runtime类中可以直接通过exec()方法创建一个新的子进程,而如果想要对这个子进程进行控制,则可以依据Process类来完成!

Runtime使用技巧

  • 要注意安全性问题,特别是在处理用户输入时要做好验证和过滤;
  • 避免滥用 Runtime 类的方法,合理利用其功能并进行适当的优化以提高性能;
  • 避免频繁创建 Runtime 类的实例,可以通过 getRuntime 方法获取单例实例,以减少资源消耗;
  • 在调用 exec 执行外部命令时,注意处理输入、输出流以避免阻塞;
  • 使用 addShutdownHook 方法注册终止钩子,确保程序退出前能够释放资源。

应用场景

Java Runtime 在需要与底层操作系统进行交互的情况下常被使用:

  • 执行外部命令,如运行系统命令行工具或执行脚本
  • 动态加载本机库文件,与本地代码进行交互;
  • 监听JVM关闭事件,执行清理活动或保存状态等操作。

实例1:通过Runtime类创建新进程或者o执行shell,python脚本

//Runtime类
public class Application {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();//获取Runtime类的对象实例
        //每当通过Runtime启动了一个进程之后实际上会返回一个进程对象
        Process process = runtime.exec("notepad.exe");//打开Windows系统中的记事本
        Thread.sleep(2000);//进程运行2秒
        process.destroy();//销毁进程
    }
}

import java.io.IOException;

public class RuntimeExample {
    public static void main(String[] args) throws IOException {

        // getRuntime - 返回与当前 Java 应用程序相关的运行时对象
        Runtime runtime = Runtime.getRuntime();

        // exec - 在单独的进程中执行指定的字符串命令
        Process process = runtime.exec("echo Hello World");

        // gc - 运行垃圾回收器
        runtime.gc();

        // load - 加载关联的本地库
        runtime.load("/path/to/library.so");

        // loadLibrary - 加载具有指定名称的本地库
        runtime.loadLibrary("mylibrary");

        // freeMemory - 返回Java虚拟机中的可用的内存量
        long freeMemory = runtime.freeMemory();
        System.out.println("Free memory: " + freeMemory + " bytes");

        // maxMemory - 返回Java虚拟机试图使用的最大内存量
        long maxMemory = runtime.maxMemory();
        System.out.println("Max memory: " + maxMemory + " bytes");

        // totalMemory - 返回Java虚拟机中的堆的当前大小
        long totalMemory = runtime.totalMemory();
        System.out.println("Total memory: " + totalMemory + " bytes");

        // runFinalization - 强制终止正在等待的所有的用户定义的对象的finalize方法
        runtime.runFinalization();

        // traceInstructions - 启用/禁用指令跟踪
        runtime.traceInstructions(true);

        // traceMethodCalls - 启用/禁用方法调用跟踪
        runtime.traceMethodCalls(true);

        // availableProcessors - 返回可用的处理器数目
        int numProcessors = runtime.availableProcessors();
        System.out.println("Available processors: " + numProcessors);

        // addShutdownHook - 注册在JVM关闭时执行的钩子程序
        Thread thread = new Thread() {
            public void run() {
                System.out.println("Shutting down JVM...");
            }
        };
        runtime.addShutdownHook(thread);

        // exit - 终止当前正在运行的Java虚拟机
        runtime.exit(0);

        // removeShutdownHook - 注销先前通过addShutdownHook方法注册的钩子
        runtime.removeShutdownHook(thread);

        // halt - 强制终止当前虚拟机,非标准化暴力关机,不会正常释放资源
        runtime.halt(1);
    }
}

Free memory: 126201960 bytes
Max memory: 1866465280 bytes
Total memory: 126877696 bytes
Available processors: 12
Shutting down JVM...

通过以上示例,我们可以了解每个 Runtime 类的方法和功能。请注意,有些方法涉及到I/O操作或系统资源,需要显式处理异常或适当的关闭流。另外,本例中的部分方法可能会导致程序终止或环境变化,请谨慎使用,并根据自己的需求进行适当的调整和优化。

特别的对于 runtime.load("/path/to/library.so");与 runtime.loadLibrary("mylibrary");这两个方法在执行之前,先确保已经在正确对应位置创建好了本地库文件,否则调试时可能出现下列异常:

java.lang.UnsatisfiedLinkError: Expecting an absolute path of the library: /path/to/library.so 
或
java.lang.UnsatisfiedLinkError: no mylibrary in java.library.path


执行python部脚本

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();
        }
    }
}


上面代码首先通过 Runtime.getRuntime() 方法获取运行时对象,这是一个表示当前 Java 应用程序相关的运行时环境的单例对象。然后使用 runtime.exec() 方法执行外部脚本命令,例如 "python script.py"。

通过 Process.getInputStream() 方法获取外部进程的输入流,并使用 InputStreamReader 和 BufferedReader 对象读取脚本的输出结果。在循环中,处理并打印每一行输出。

最后,通过 process.waitFor() 方法等待外部进程结束,并获取退出值。根据约定,退出值为 0 表示成功执行,而非零值表示出现错误。根据退出值,可以确定外部脚本是成功执行还是失败。

这个实例展示了如何在 Java 中使用 Runtime 类来执行外部脚本,并处理输出结果和异常情况。注意,使用 runtime.exec() 执行外部命令需要谨慎处理,特别是针对从用户输入或不受信任的源代码中派生的命令。建议对脚本及其参数进行适当的验证和限制,以确保安全性。

实例2:观察内存数据

        在以后实际的项目开发过程中,对于Runtime类来讲比较重要的操作就在于内存信息的动态获取,整个Java程序的正确执行都需要依靠内存的支持,如果内存不能保证正确运行环境,那么程序就会出现严重的性能问题!!

//Runtime类
public class Application {
    //2.观察内存数据
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        System.out.println("【1】MaxMemory = "+ runtime.maxMemory());
        System.out.println("【1】TotalMemory = "+ runtime.totalMemory());
        System.out.println("【1】FreeMemory = "+ runtime.freeMemory());
    }
}

【1】MaxMemory = 1875378176
【1】TotalMemory = 126877696
【1】FreeMemory = 124193056

示例:

//	范例 : 观察gc() 使用前后的内存占用率


public class TestDemo {
	public static void main(String[] args) throws Exception {
		Runtime run= Runtime.getRuntime();	// 取得Runtime 类的实例化对象
		String str = "";
		for (int x=0; x<2000; x++){
			str += x; 	//产生大量垃圾
		}
		System.out.println("【垃圾处理前内存量】MAX = " + run.maxMemory());
		System.out.println("【垃圾处理前内存量】TOTAL = " + run.totalMemory());
		System.out.println("【垃圾处理前内存量】FREE = " + run.freeMemory());
		run.gc();                                             //释放垃圾空间
		System.out.println("【垃圾处理后内存量】MAX = " + run.maxMemory());
		System.out.println("【垃圾处理后内存量】TOTAL = " + run.totalMemory());
		System.out.println("【垃圾处理后内存量】FREE = " + run.freeMemory());
	}
}

【垃圾处理前内存量】MAX = 259522560
【垃圾处理前内存量】TOTAL = 16252928
【垃圾处理前内存量】FREE = 11908336  (空闲空间减少)
【垃圾处理后内存量】MAX = 259522560
【垃圾处理后内存量】TOTAL = 16318464
【垃圾处理后内存量】FREE = 15731872  (空闲空间释放)

注意:

  1. MaxMemory默认是物理内存的四分之一,TotalMemory默认是物理内存的六十四分之一

    如下图为本机的物理内存

  2. 以上代码可以直接返回具体的内存大小数据,返回的内存大小单位是字节,在Java程序设计中,所有与文件大小有关的操作都会与long数据类型进行定义;

  3. 整个JVM(虚拟机)进程的可用最大内存(MaxMemory) > 默认的可用内存(TotalMemory,该值会发生改变,但最终不超过MaxMemory) > 空闲内存(FreeMemory)

  4. 如果空闲内存不足者TotalMemory也会进行动态的扩充(这种动态的扩充机制实际上是非常伤害程序的)

实例3:动态获取内存信息

public class Application {
    //2.观察内存数据
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        System.out.println("【1】垃圾产生前的内存信息:MaxMemory = "+ runtime.maxMemory());
        System.out.println("【1】垃圾产生前的内存信息:TotalMemory = "+ runtime.totalMemory());
        System.out.println("【1】垃圾产生前的内存信息:FreeMemory = "+ runtime.freeMemory());
        String message = "abcd";//定义字符串
        for(int x = 0;x < 20;x++){
            message +=message + x +"\n";//产生大量的垃圾
        }
        //runtime.gc();//手工进行垃圾清除
        System.out.println("【2】垃圾产生后的内存信息:MaxMemory = "+ runtime.maxMemory());
        System.out.println("【2】垃圾产生后的内存信息:TotalMemory = "+ runtime.totalMemory());
        System.out.println("【2】垃圾产生后的内存信息:FreeMemory = "+ runtime.freeMemory());
    }
}

【1】垃圾产生前的内存信息:MaxMemory = 1875378176
【1】垃圾产生前的内存信息:TotalMemory = 126877696 //注意变化
【1】垃圾产生前的内存信息:FreeMemory = 124193056  //注意变化
    
【2】垃圾产生后的内存信息:MaxMemory = 1875378176
【2】垃圾产生后的内存信息:TotalMemory = 160432128 //注意变化
【2】垃圾产生后的内存信息:FreeMemory = 94067232   //注意变化

        通过当前程序执行可知,此时可以使用的堆内存空余空间已经有所减少,随着程序垃圾的产生一定会浪费大量的堆内存空间,这样就可以通过Runtime类动态的获取内存信息!

报错:

如果此时内存空间已经被垃圾空间严重占满了,并且来不及进行扩充,则会出现以下的报错信息,

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  • 这属于内存溢出的问题
  • 这种OOM问题需要通过JVM调优来完成

实例4:gc()调用

        如果清除程序可能会存在大量的垃圾,那么最佳做法就是进行垃圾清除,可以利用Runtime类中提供的gc()方法来完成此类操作!

        当使用gc()进行垃圾清除之后可以发现空闲的空间已经被回收了,这样可以便于后续程序的高效咨执行

public class Application {
    //2.观察内存数据
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        System.out.println("【1】垃圾产生前的内存信息:MaxMemory = "+ runtime.maxMemory());
        System.out.println("【1】垃圾产生前的内存信息:TotalMemory = "+ runtime.totalMemory());
        System.out.println("【1】垃圾产生前的内存信息:FreeMemory = "+ runtime.freeMemory());
        String message = "abcd";//定义字符串
        for(int x = 0;x < 20;x++){
            message +=message + x +"\n";//产生大量的垃圾
        }
        runtime.gc();//手工进行垃圾清除
        System.out.println("【2】GC调用后的内存信息:MaxMemory = "+ runtime.maxMemory());
        System.out.println("【2】GC调用后的内存信息:TotalMemory = "+ runtime.totalMemory());
        System.out.println("【2】GC调用后的内存信息:FreeMemory = "+ runtime.freeMemory());
    }
}
【1】垃圾产生前的内存信息:MaxMemory = 1875378176
【1】垃圾产生前的内存信息:TotalMemory = 126877696 //注意变化
【1】垃圾产生前的内存信息:FreeMemory = 124193056  //注意变化
    
【2】GC调用后的内存信息:MaxMemory = 1875378176
【2】GC调用后的内存信息:TotalMemory = 160432128  //注意变化
【2】GC调用后的内存信息:FreeMemory = 145912272   //注意变化

实例5:动态加载类

下面是一个演示如何使用 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 对象,用于表示当前Java应用程序相关的运行时环境。

然后,通过获取 runtime 对象的类加载器(runtime.getClass().getClassLoader())和指定的类名,使用 loadClass() 方法从类加载器中动态加载需要的类。

接下来,通过反射实例化被加载的类,使用 dynamicClass.newInstance() 创建了一个类的实例,并使用反射获取被加载类中的方法(例如 doSomething()),并使用 method.invoke(instance) 调用该方法。

这个实例演示了如何使用 Runtime 类动态加载类,并调用其中的方法。需要注意,动态加载类在某些场景下是有用的,但也应谨慎使用。合理地处理异常情况以及对类名、方法等进行适当的验证和限制是非常重要的。

总结:

        Java之中的所有GC属于守护线程(守护线程是伴随主线程而存在的),主要目的是进行垃圾的收集以及堆内存空间释放。

        Java Runtime 类作为Java虚拟机的实例,对于动态地控制运行时环境至关重要。通过提供各种功能和方法,例如执行外部命令、管理内存和垃圾回收,以及动态加载和卸载类,Runtime 类使得Java程序能够更加灵活地与底层系统进行交互。

        然而,开发人员需要注意安全性、性能优化及平台依赖性等方面,以确保代码的可靠性和效率。熟练使用 Runtime 类,并根据具体需求进行合理的优化,将有助于实现更强大和高效的Java应用程序。

        如果要进行gc的处理,在JVM(虚拟机)进程中有两种处理形式:

  • 自动的垃圾收集(需要清除整个的JVM体系结构)
  • 手工的垃圾收集(Runtime类中的gc()方法)

网站公告

今日签到

点亮在社区的每一天
去签到