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

1.概念简介

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

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


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的基础定义


    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<String> 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<String> 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一样的操作流程)


mac操作

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


安装插件

插件安装后,我们可以就可以在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链接


链接远程tomcat

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


image.png

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

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


    本地监控

对本地的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,里边有大部分场景的样例脚本:

image.png

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容