设为首页 - 加入收藏 ASP站长网(Aspzz.Cn)- 科技、建站、经验、云计算、5G、大数据,站长网!
热搜: 数据 创业者 手机
当前位置: 首页 > 综合聚焦 > 编程要点 > 语言 > 正文

版本历史与代码示例之JMX

发布时间:2021-11-04 10:07 所属栏目:51 来源:互联网
导读:前言 对于一个正在运行的Java程序,我们希望管理和监控它的状态,如:内存、CPU使用率、线程数、垃圾回收情况等等,这时使用JMX便是一种非常优雅的解决方案。你可能听过JConsole、VisualVM等性能调优工具,殊不知哥俩底层都依赖于它,本文就带你走进Java的管
前言
对于一个正在运行的Java程序,我们希望管理和监控它的状态,如:内存、CPU使用率、线程数、垃圾回收情况等等,这时使用JMX便是一种非常优雅的解决方案。你可能听过JConsole、VisualVM等性能调优工具,殊不知哥俩底层都依赖于它,本文就带你走进Java的管理扩展:JMX。
 
 
 
JMX既是Java管理系统的一个标准,一个规范;也是一个接口,一个“框架”。有标准、有规范是为了让开发者可以定制开发自己的扩展功能,而且作为一个“框架”来讲,JDK 已经帮我们实现了常用的功能,尤其是对JVM本身的监控和管理。
 
所属专栏【方向盘】
-Java EE
 
相关下载
【本专栏源代码】:https://github.com/yourbatman/FXP-java-ee
【技术专栏源代码大本营】:https://github.com/yourbatman/tech-column-learning
【女娲Knife-Initializr工程】访问地址:http://152.136.106.14:8761
【程序员专用网盘】公益上线啦,注册送1G超小容量,帮你实践做减法:https://wangpan.yourbatman.cn
【Java开发软件包(Mac)】:https://wangpan.yourbatman.cn/s/rEH0 提取码:javakit
版本约定
Java EE:6、7、8
Jakarta EE:8、9、9.1
正文
 
 
JMX
JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。我们可以使用jmx对程序的运行状态进行监控和管理。
 
JMX是Java EE内嵌(被内嵌进JRE里面了)的一套标准的代理和服务,也就是说只要遵循这个接口标准,那么就可以管理和监控我们的应用程序。为了标准化管理和监控,Java平台使用JMX作为管理和监控的标准接口,任何程序,只要按JMX规范访问这个接口,就可以获取所有管理与监控信息。常用的运维监控如Zabbix、Nagios等工具对JVM本身的监控都是通过JMX获取的信息。
 
JMX是一个标准接口,不但可以用于管理JVM,还可以管理应用程序自身。
 
这是官方给出的JMX架构图:
 
 
 
由图可知,JMX技术分为三层:
 
设备/资源层:这些被管理的资源就是MBean/MXBean们
 
代理层:MBeanServer就是代理层的最核心组件,MBean们均注册到此处,让它代理统一对外提供功能服务
 
代理层其实就是一个独立的Java线程
远程管理层:JMX技术可以通过多种不同的方式去访问,每个适配器通过一个给定的协议来访问MBeanServer中注册的所有MBean们,比如Html协议、Http协议、JDK自己实现的RMI协议等
 
什么是MBean
MBean = Managed Bean。其的本质就是我们经常说的Java Bean,遵循Java Bean规范,只是它专门用于JMX所以称为MBean。JMX把所有被管理的资源都称为MBean,全部交由MBeanServer管理,JVM会将自身各种资源(CPU、内存等)注册到JMX中,自己也可自定义MBean然后放进去,从而达到自定义监控的能力。最后对外通过暴露RMI/HTTP协议提供访问。
 
说明:JMX不需要安装任何额外组件,也不需要第三方库,因为MBeanServer已经内置在JavaSE标准库中了。
JDK提供的MBean主要都在java.lang.management 和 javax.management这两个包里面,MBean一共分为四种类型:
 
 
 
1.Standard MBean:最常用、最简单的一种,结构和普通Java Bean没有区别,管理接口通过方法名来描述。它只要遵循一定的命名规则即可注册进MBeanServer
 
定义一个接口,该接口名称必须为xxxMBean(必须以MBean为后缀结尾)
写该接口的实现类,然后将此实现类注册进MBeanServer即可
2.Dynamic MBean:在运行期才定义它的属性和方法,也就是说它有什么属性和方法是可以动态改变的。所有的动态MBean必须实现DynamicMBean接口,然后注册上去即可
 
动态Bean的辅助类主要有MBeanConstructorInfo、MBeanAttributeInfo、MBeanOperationInfo等等
动态Bean是一种妥协的产物,因为已经存在一些MBean,而将其改造成标准MBean比较费力而且不切实际,所以就用动态Bean妥协一下。自定义的时候几乎不会使用
3.Open MBean:Open MBeans需实现DynamicMBean接口,与动态Bean不同的是提供了更复杂的metadata数据,和在接口中,只使用了几种预定义的通用数据类型:OpenMBeanInfo、OpenMBeanOperationInfo、OpenMBeanConstructorInfo、OpenMBeanParameterInfo、OpenMBeanAttributeInfo
 
4.Model MBean:如果不能修改已有的Java类,使用它是个不错的选择。通过实现接口javax.management.modelmbean.RequiredModelMBean,我们要做的就是实例化该类然后注册即可实现对资源的管理
 
编写Model MBean的最大挑战是告诉Model MBean对象托管资源的那些熟悉和方法可以暴露给代理层,ModelMBeanInfo对象描述了将会暴露给代理的构造函数、属性、操作甚至是监听器。
话外音:一般情况下,我们只需要了解Standard MBean即可。
 
MBean和MXBean区别
MBean与MXBean的区别主要是在于在接口中会引用到一些其他类型的类(复合类型)时,其表现方式的不一样。
 
MBean:属性不能是复合类型/自定义类型,否则不能被识别
MXBean:属性可以是自定义类型。如JDK自带的MemoryMXBean中定义了heapMemoryUsage属性,它就是复合类型
什么是MBeanServer
顾名思义:用于管理MBean的“服务器”。一般来讲一个JVM只有一个MBeanServer(通过ManagementFactory.getPlatformMBeanServer()这个API来获得),用于管理该JVM内所有的MBean,并且对外提供服务。
 
倘若需要多个MBeanServer(比如不同的domain),你可通过MBeanServerFactory.newMBeanServer(String domain)这个API来创建。
 
什么是Connector和Adaptor
当MBean都注册到MBeanServer上面后,功能已经具备,就可以通过协议把这些功能暴露出去啦。针对不同的协议就有其对应的Connector或者Adaptor(这里可把Connector和Adaptor认为是相同的角色)。
 
所以,只要有连接器/适配器,可以通过多种协议将功能暴露出去,如Http协议、Saop协议、RMI等。JDK默认实现的只有基于RMI的javax.management.remote.rmi.RMIConnector,像JConsole、VisualVM这类工具默认是可直接连接访问的。
 
注意:Spring Boot Actuator对其管理、监控等端点提供Http和RMI(JMX)两种访问方式,但是其Http方式并非实现了Connector/Adaptor哦,甚至来讲基于Http的操作方式都并非JMX方式(实为Endpoint方式),不要让某些文章给误导了哈。
 
既然有Http,JMX意义何在?
这个问题一度困扰过我,没太想明白JMX存在的意义。诚然,JMX能完成的任务通过Http都能完成,只不过某些情况下用JMX来做会更加方便。简单来讲,Http更重,JMX更轻。
 
Http是一个更加抽象、应用面更广泛、功能更强大的协议/服务,因此做的工作也会多一些。比如光方法它就有Get、Post、Put、Delete等等
JMX是一个更加具体、应用面不那么广、功能也没有Http强大的协议/服务。所以它的优点是轻便、好用
JMX的特点决定了它非常非常适合做资源监控,因此各大监控组件、框架为了监控JVM的运行情况,都会把JMX当做首选,而Http协议只是为了产品化的备选。
 
jmx被内嵌入jdk/jre自带,无需额外导包 
版本历程
 
 
JMX伴随着JDK 5的发布而出现,之后其实也几乎没有变化,如下所示。
 
Java EE 5:
 
 
 
Java EE 8:
 
 
 
JSR 3的内容基本和JSR 255没变,可认为一样。
 
 生存现状
高阶必备。比如做监控、JVM性能分析、调优、问题定位等。
 
实现(框架)
 
代码示例
虽说Demo示例才是重头戏,但由于本文并非JMX专题,所以只会示例原生方式使用JMX,至于在Spring、Spring Boot、借助commons-modeler等使用,点到即止。
 
直接使用JDK内置的MBean/MXBean
JDK内置了“大量”的MBean,供你直接使用:
 
ClassLoadingMXBean:Java虚拟机的类加载系统。
CompilationMXBean:Java虚拟机的编译系统。
MemoryMXBean:Java虚拟机的内存系统。
ThreadMXBean:Java虚拟机的线程系统。
RuntimeMXBean:Java虚拟机的运行时系统。
OperatingSystemMXBean:Java虚拟机在其上运行的操作系统。
GarbageCollectorMXBean:Java虚拟机中的垃圾回收器。
MemoryManagerMXBean:Java虚拟机中的内存管理器。
MemoryPoolMXBean:Java虚拟机中的内存池。
这些实例通过ManagementFactory都可拿到。
 
@Test 
public void test1() { 
    ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean(); 
    ObjectName objectName = classLoadingMXBean.getObjectName(); 
    long totalLoadedClassCount = classLoadingMXBean.getTotalLoadedClassCount(); 
    int loadedClassCount = classLoadingMXBean.getLoadedClassCount(); 
    long unloadedClassCount = classLoadingMXBean.getUnloadedClassCount(); 
 
    System.out.println("objectName:" + objectName); 
    System.out.println("JVM启动共加载的Class类总数(一个类被加载多次):" + totalLoadedClassCount); 
    System.out.println("JVM当前状态加载Class类总数:" + loadedClassCount); 
    System.out.println("JVM还未加载的Class类总数:" + unloadedClassCount); 
 
objectName:java.lang:type=ClassLoading 
JVM启动共加载的Class类总数(一个类被加载多次):1743 
JVM当前状态加载Class类总数:1743 
JVM还未加载的Class类总数:0 
@Test 
public void test2() { 
    RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); 
    ObjectName objectName = runtimeMXBean.getObjectName(); 
    String name = runtimeMXBean.getName(); 
    // JVM信息 
    String specVendor = runtimeMXBean.getSpecVendor(); 
    String specName = runtimeMXBean.getSpecName(); 
    String specVersion = runtimeMXBean.getSpecVersion(); 
 
    String bootClassPath = runtimeMXBean.getBootClassPath(); 
    String classPath = runtimeMXBean.getClassPath(); 
    String libraryPath = runtimeMXBean.getLibraryPath(); 
 
    System.out.println("objectName:" + objectName); 
    System.out.println("运行期名称name:" + name); 
    System.out.println("当前JVM进程ID:" + name.split("@")[0]); 
    System.out.println("虚拟机信息:" + specVendor + ":" + specName + ":" + specVersion); 
    // System.out.println("bootClassPath:" + bootClassPath); 
    // System.out.println("classPath:" + classPath); 
    // System.out.println("libraryPath:" + libraryPath); 
 
objectName:java.lang:type=Runtime 
运行期名称name:9966@YourBatman-MBA.local 
当前JVM进程ID:9966 
虚拟机信息:Oracle Corporation:Java Virtual Machine Specification:1.8 
RuntimeMXBean它常被用来获取JVM进程ID。
 
@Test 
public void test3() { 
    // JVM内存情况 
    MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); 
    ObjectName objectName = memoryMXBean.getObjectName(); 
    MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage(); 
    MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage(); 
 
    System.out.println("objectName:" + objectName); 
    System.out.println("已使用堆内存:" + heapMemoryUsage); 
    System.out.println("已使用非堆内存:" + nonHeapMemoryUsage); 
 
    // 操作系统的内存情况? 
    long l = Runtime.getRuntime().totalMemory(); 
    long l1 = Runtime.getRuntime().freeMemory(); 
 
objectName:java.lang:type=Memory 
已使用堆内存:init = 268435456(262144K) used = 24183016(23616K) committed = 257425408(251392K) max = 3817865216(3728384K) 
已使用非堆内存:init = 2555904(2496K) used = 12547040(12252K) committed = 13959168(13632K) max = -1(-1K) 
下面OperatingSystemMXBean是操作系统层面的信息:
 
@Test 
public void test4() { 
    OperatingSystemMXBean osbean = ManagementFactory.getOperatingSystemMXBean(); 
    System.out.println("操作系统体系结构:" + osbean.getArch()); 
    System.out.println("操作系统名字:" + osbean.getName()); 
    System.out.println("处理器数目:" + osbean.getAvailableProcessors()); 
    System.out.println("操作系统版本:" + osbean.getVersion()); 
 
    ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); 
    System.out.println("总线程数:" + threadBean.getThreadCount());// 
 
操作系统体系结构:aarch64 
操作系统名字:Mac OS X 
处理器数目:8 
操作系统版本:11.6 
总线程数:4 
自定义MBean - 本地线程连接
除了以上系统自带的MBean/MXBean,更重要的是自定义MBean:将普通User实体类暴露成为一个MBean。
 
/** 
 * MBean资源通过接口暴露,【一定必须】以MBean结尾才算一个MBean 
 * 
 * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a> 
 * @site https://yourbatman.cn 
 * @date 2021/10/18 21:14 
 * @since 0.0.1 
 */ 
public interface UserMBean { 
 
    String getName(); 
 
    void setName(String name); 
 
    void setAge(int age); 
User实体类必须实现此接口:
 
@Getter 
@Setter 
public class User implements UserMBean { 
 
    private String name; 
    private int age; 
 
将此MBean注册到MBeanServer:
 
@Test 
public void test1() throws Exception { 
    MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); 
    ObjectName objectName = new ObjectName("com.yourbatman:type=UserXXX"); // 名字可任意取,但最好见名知意 
    mBeanServer.registerMBean(new User(), objectName); 
 
    // 线程保活,方便获取MBean 
    Thread.sleep(Long.MAX_VALUE); 
使用JConsole即可连接到此线程:
 
 
 
链接上后即可以进行“操作”啦:
 
 
 
自定义MBean - 远程连接
除了通过本地进程连接外,JDK原生还支持通过RMI协议暴露,供以连接。我们只需要将其通过RMI协议暴露出去即可:
 
JMX并不限制通过上面协议暴露出去,只是JDK默认只实现了RMI协议,够用就好!
 
@Test 
public void test2() throws Exception { 
    MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); 
 
    LocateRegistry.createRegistry(9090); // 这一步不能少,不需要返回值 
    JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://127.0.0.1:9090/userXXX"); 
    JMXConnectorServer cntorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mBeanServer); 
    cntorServer.start(); 
 
    ObjectName objectName = new ObjectName("com.yourbatman:type=UserXXX"); 
    mBeanServer.registerMBean(new User(), objectName); 
 
    // 线程保活,方便获取MBean 
    Thread.sleep(Long.MAX_VALUE); 
使用JConsole通过RMI协议远程连接:
 
 
 
 
 
自定义MBean - 编程方式连接
除了通过JConsole这类工具连接外,通过编程方式也是能够通过JMX搞的。毕竟RMI协议用Java可以直接操作嘛:
 
@Test 
public void test1() throws Exception { 
    JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://127.0.0.1:9090/userXXX"); 
    JMXConnector conn = JMXConnectorFactory.connect(url, null); 
 
    UserMBean userMBean = JMX.newMBeanProxy(conn.getMBeanServerConnection(), new ObjectName("com.yourbatman:type=UserXXX"), UserMBean.class); 
    System.out.println("通过RMI协议拿到:" + userMBean); 
    System.out.println("user的名字:" + userMBean.getName()); 
 
    conn.close(); 
 
通过RMI协议拿到:MBeanProxy(javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@706a04ae[com.yourbatman:type=UserXXX]) 
user的名字:null 
注意:执行client前请确保Server端已启动,否则会连接失败!
 
自定义MBean - 远程连接(启动参数方式)
对于已打好的Jar包/war包,不可能改其代码再让其支持JMX远程连接。这时,我们可以通过启动参数方式来开启远程连接。这些启动参数一般放在命令行、环境变量里。
 
java  
 
-Djava.rmi.server.hostname=你的主机 
-Dcom.sun.management.jmxremote.port=端口号 
-Dcom.sun.management.jmxremote.ssl=false 
-Dcom.sun.management.jmxremote.authenticate=false 
 
-jar xxx.jar 
总结
JMX是Java EE规范、JDK提供的一个小工具,使用起来不难但能量不小,推荐你可花点时间学习学习、写一写、用一用以发挥效用,向高级进阶。
 
其实JMX并不“稀有”,它存在于很多流行软件/中间件里:Kafka、Spring Boot、RocketMQ,以及Logback都可看到JMX的影子,实现了很好的功能。如:使用JMX(无需重启)动态更改Logback的日志级别。
 
关于JMX的内容,本文点到即止。若你在Spring/Spring Boot场景下开发,依托于Spring的抽象能力,“集成/使用”JMX将变得更加容易,期待你的探索,以后有机会我们再聊此专题。

(编辑:ASP站长网)

    网友评论
    推荐文章
      热点阅读