JMX之介绍和mac环境使用介绍

1.概念简介

JMX(Java Management Extensions)是一个为应用程序植入管理功能的框架。JMX是一套标准的代理和服务,
实际上,用户可以在任何Java应用程序中使用这些代理和服务实现管理。主要用于对JAVA应用程序和JVM进行监控和管理。

JConsole和JVisualVM中能够监控到JAVA应用程序和JVM的相关信息都是通过JMX实现的。看下网上的一张结构图


JMX之介绍和mac环境使用介绍_第1张图片
jmx

三层结构,分别负责通信,代理,管理资源bean。

  • 最上的通信层:Agent如何被远端用户访问的细节。它定义了一系列用来访问Agent的接口和组件,包括Adapter和Connector的描述。

  • 中间的代理层:管理相应的资源,并且为远端用户提供访问的接口。Agent层构建在Intrumentation层之上,并且使用并管理 Instrumentation层内部描述的组件。Agent层主要定义了各种服务以及通信模型。该层的核心是一MBeanServer,所有的MBean都要向它注册,才能被管理。注册在MBeanServer上的MBean并不直接和远程应用程序进行通信,他们通过协议适配器(Adapter)和连接器(Connector)进行通信。通常Agent由一个MBeanServer和多个系统服务组成。JMX Agent并不关心它所管理的资源是什么

  • 基础管理bean:主要包括了一系列的接口定义和描述如何开发MBean的规范。通常JMX所管理的资源有一个或多个MBean组成,因此这个资源可以是任何由Java语言开发的组件,或是一个JavaWrapper包装的其他语言开发的资源。

2. 使用举例

很多开源框架都会用到jmx监控系统状态,比如我们熟悉的tomcat。下面举个简单的例子,说明我们实际使用场景。

  • 需求,做一个自定义的classloader类,进行加载我指定包路径下的类,并管理加载了这些类:

  • MBean定义
      根据Mbean的基础定义


    JMX之介绍和mac环境使用介绍_第2张图片
    image.png

我们使用最基本的standard MBean定义接口,此时有以下要求:

  1. 接口和实现类必须在同一包下
    2.接口名字为TestLoaderMBean,那么实现类必须叫TestLoader(MBean前字符串),一个单词不许错,否则会报错

因此,我们接口定义

package jmx.beans;

public interface TestLoaderMBean {

    /**
     * 获取loader名
     * @return
     */
    String loaderName();

    /**
     * 获取loader总共加载的类数量
     * @return
     */
    int loaderClassSize();

    int getLoaderClassSize();


    /**
     * 获取加载了的所有类名称
     * @return
     */
    String loaderClassesNames();

    /**
     * 加载所有类
     */
    void loaderClasses();

    /**
     * 获取用户指定加载的包路径
     * @return
     */
    String packageName();


    /**
     * 销毁此加载器
     */
    void destory();
}

接口定义了loader的管理规范,声明了对应此loader的管理工作,然后定义其实现类

package jmx.beans;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class TestLoader implements TestLoaderMBean {

    private String name;

    private int loaderClassSize;


    private List loaderClasss;

    private String packageName;


    public TestLoader(String name, String packageName) {
        this.name = name;
        this.packageName = packageName;
    }

    public TestLoader( ) {
    }


    @Override
    public String loaderName() {
        return name;
    }

    @Override
    public int getLoaderClassSize() {
        return loaderClassSize;
    }

    @Override
    public int loaderClassSize() {
        return loaderClassSize;
    }

    @Override
    public String loaderClassesNames() {
        StringBuffer sb=new StringBuffer();
        if(loaderClasss!=null){
            loaderClasss.stream().forEach(s->{
                sb.append(s).append(";\n");
            });
        }
        return sb.toString();

    }


    @Override
    public String packageName() {
        return packageName;
    }

    public void setName(String name) {
        this.name = name;
    }


    public void setLoaderClasss(List loaderClasss) {
        this.loaderClasss = loaderClasss;
        if (loaderClasss != null) {
            loaderClassSize = loaderClasss.size();
        }
    }


    @Override
    public void loaderClasses() {
        System.out.println("加载" + packageName + "所有类");
        Random random = new Random(3);
        int i = random.nextInt(3);
        try {
            TimeUnit.SECONDS.sleep(i);

            String class1 = "com.class1";
            String class2 = "com.class2";
            String class3 = "com.class3";
            if (loaderClasss == null) {
                loaderClasss = new ArrayList<>();
            }
            loaderClasss.add(class1);
            loaderClasss.add(class2);
            loaderClasss.add(class3);
            loaderClassSize = loaderClasss.size();
            System.out.println("size"+loaderClassSize);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void destory() {
        loaderClasss = null;
        loaderClassSize = 0;
        System.out.println("销毁所有类");
    }
}

模拟类中,注意到getLoaderClassSize()和loaderClassSize()方法是重复的,实际上这样是有区别的,一会我们会用到。
最后需要注册一个代理服务

package jmx;


import jmx.beans.TestLoader;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;

public class LocalAgent {


    private MBeanServer mbs;

    public LocalAgent() throws Exception {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

        ObjectName mbeanName = new ObjectName("jmx:type=testLoader");

        TestLoader mbean = new TestLoader("test","com.class");

        mbs.registerMBean(mbean, mbeanName);

        Thread.sleep(Long.MAX_VALUE);


    }



    public static void main(String args[]) throws Exception {
        LocalAgent agent = new LocalAgent();
    }

}

这样我们就是实现了简单jmx的管理示例,当然MBeanServer是支持rmi、html等远程监控协议,如rmi:

package jmx;

import jmx.beans.TestLoader;

import javax.management.*;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;

public class RmiAgent
{
    public RmiAgent() {

        try {
            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

            ObjectName mbeanName = new ObjectName("jmx:type=testLoader");

            TestLoader mbean = new TestLoader("test","com.class");

            mbs.registerMBean(mbean, mbeanName);

            int port=9000;

            //这个步骤很重要,注册一个端口,绑定url后用于客户端通过rmi方式连接JMXConnectorServer
            LocateRegistry.createRegistry(port);
            //URL路径的结尾可以随意指定,但如果需要用Jconsole来进行连接,则必须使用jmxrmi
            String urlStr="service:jmx:rmi:///jndi/rmi://localhost:"+port+"/jmxrmi";
            JMXServiceURL url = new JMXServiceURL(urlStr);
            JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
            jcs.start();
            System.out.println("rmi start: "+urlStr);
        } catch (MalformedObjectNameException e) {
            e.printStackTrace();
        } catch (InstanceAlreadyExistsException e) {
            e.printStackTrace();
        } catch (MBeanRegistrationException e) {
            e.printStackTrace();
        } catch (NotCompliantMBeanException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        RmiAgent rmiAgent=new RmiAgent();
    }
}

当然,我们实际使用的是tomcat等容器服务,所以说下远程tomcat开启jmx监控服务。其实,很简单,只需要,我们在自定义等setnev.sh中定义

#开启服务
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote
#指定ip
 -Djava.rmi.server.hostname=172.18.162.10  
#指定端口
-Dcom.sun.management.jmxremote.port=9999 
#指定是否需要密码,如果为true,需要指定用户密码
-Dcom.sun.management.jmxremote.authenticate=false 
#是否需要ssl
 -Dcom.sun.management.jmxremote.ssl=false"

此时再启动tomcat,就是自动启动tomcat等jmx了。

对于客户端来说,我们有很多,常用的如JVisualVM、JConsole等。下面说下两种的使用方法。mac环境下,装好jdk后,这些都是已经集成好的(windows在java的bin目录下可以找到相应exe程序),打开终端输入JVisualVM就会自动启动JVisualVM客户端(对应JConsole一样的操作流程)


JMX之介绍和mac环境使用介绍_第3张图片
mac操作

JVisualVM功能是JConsole的升级版,包含了很多功能,可以查看内存、cpu、线程、生成堆快照.....等等。在JVisualVM使用JConsole ,安装JConsole插件即可,如下我安装了JConsole、MBean、Btrace等插件:


JMX之介绍和mac环境使用介绍_第4张图片
安装插件

插件安装后,我们可以就可以在JVisualVM中使用JConsole、MBean、Btrace了。

  1. 看下对远程tomcat对监控
    上面,我们已经在tomcat的bin目录下的setenv.sh文件中加入了启动jmx的脚本,我们已经可以通过客户端链接了,链接地址看下配置文件:
#指定ip
 -Djava.rmi.server.hostname=172.18.162.10  
#指定端口
-Dcom.sun.management.jmxremote.port=9999

端口号是9999,我们用JVisualVM链接


JMX之介绍和mac环境使用介绍_第5张图片
链接远程tomcat

这样我们就可以通过jmx来管理远程tomcat服务了,看下tomcat自带的一些jmx使用


JMX之介绍和mac环境使用介绍_第6张图片
image.png

类似这种管理类我们看到了很多,可以通过jmx添加用户,查看部署的app状态等等管理模块。

  1. 本地java服务监控
    在我们本地调试java代码时也可以使用,并且,可以使用Btrace进行代码注入。
    如上边我本地运行了一个LocalAgent的java程序


    JMX之介绍和mac环境使用介绍_第7张图片
    本地监控

对本地的java进程就可以做相应的监控操作了,并且我们安装了Btrace插件,可以通过Btrace脚本进行代码注入,如TestLoader类中有个loaderClasses方法,在不关服务的情况下,我想测一下此方法的执行时间。我们写个Btrace脚本

/* BTrace Script Template */
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TracingScript {
    /* put your code here */
   @OnMethod(
     clazz="jmx.beans.TestLoader", 
     method="loaderClasses"
   )   
   public static void onWebserviceEntry(@ProbeClassName String pcn, @ProbeMethodName String pmn) {
       println(Strings.strcat(Strings.strcat(pcn, "."), pmn));
   }

   @OnMethod(
     clazz="jmx.beans.TestLoader", 
      method="loaderClasses",
     location=@Location(Kind.RETURN)
   )   
   public static void onWebserviceReturn(@ProbeClassName String pcn , @ProbeMethodName String pmn, @Duration long d) {
       print("leaving web service ");
       println(Strings.strcat(Strings.strcat(pcn, "."), pmn));
       println(Strings.strcat("Time taken (msec) ", Strings.str(d / 1000)));
       println("==========================");
   }
}

脚本中通过onWebserviceReturn方法的@Duration参数打印了方法的执行时间,这样就在我们无需关闭服务器的情况下,添加了代理代码。但要注意Btrace执行后,java类的class并不会还原,会一直存在,所以如果是生产环境要谨慎。特别@OnMethod通配符的写法,要修改所有匹配到的类。如果想了解Btrace更多内容,更多脚本,可以参考官方文档https://github.com/btraceio/btrace,里边有大部分场景的样例脚本:

JMX之介绍和mac环境使用介绍_第8张图片
image.png

好了,上边说了这么多,都是通过jmx延伸出,我们对于应用监控的一些方法。当然还有很多,如对于远程debug的jpda、对于内存分析的mat等等,有时间可以一起讨论下。jmx给我提供的是一种监控思想,如我们要实现自己的连接池、管理器等服务,可以借鉴相应的监控管理方法。

你可能感兴趣的:(JMX之介绍和mac环境使用介绍)