关于JMX

JMX 全称是 Java Management Extensions, Java5.0开始引入,提供连接、监控和管理远程JVM的方式。

MBean

一个MBean是一个被管理的Java对象,有点类似于JavaBean,一个设备、一个应用或者任何资源都可以被表示为MBean,MBean会暴露一个接口对外,这个接口可以读取或者写入一些对象中的属性,通常一个MBean需要定义一个接口,以MBean结尾,例如:DataSourceMBean, 格式为XXXMBean,这个是规范,必须得遵守。

MBeanServer

MBean的容器,提供远程访问、命名空间管理和安全服务。

JMX 指定了在 MBeanServer 和 JMX 客户之间通信所使用的协议,协议可以在各种传输机制上运行。可以使用针对本地连接的内置传输,及通过 RMI、socket 或 SSL 的远程传输(可以通过 JMX Connector API 创建新的传输)。

一个简单的例子

编写JMX其实是非常简单。步骤如下:

1、定义一个MBean接口,这个接口的getter/setter就是要暴露和管理的接口。

package me.aragnzheng.study.jmx;

public interface HelloMBean {

	public String getName();

	public void setName(String name);

	public void printHello();

	public void printHello(String name);
}

2、实现MBean接口

package me.aragnzheng.study.jmx;

public class Hello implements HelloMBean {

	private String name;

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

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

	@Override
	public void printHello() {
		System.out.println("Hello world, " + name);

	}

	@Override
	public void printHello(String name) {
		System.out.println("Hello, " + name);
	}

}

3、暴露我们的MBean,这个就是讲MBean注册到MBeanServer。

package me.aragnzheng.study.jmx;

import java.lang.management.ManagementFactory;

import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

public class HelloAgent {
	public final static String MBEAN_NAME = "me.arganzheng.study.jmx:type=HelloJMX";

	public static void main(String[] args) throws Exception,
			NotCompliantMBeanException, InterruptedException {
		// MBeanServer server = MBeanServerFactory.createMBeanServer();
		MBeanServer server = ManagementFactory.getPlatformMBeanServer();

		ObjectName helloName = new ObjectName(MBEAN_NAME);
		server.registerMBean(new Hello(), helloName);

		System.out.println("start.....");
		Thread.currentThread().join();
	}
}

这样就可以了。然后我们就可以使用jconsole进行监控和管理了。需要注意的是这里需要使用getPlatformMBeanServer才能够被jconsole监控到,创建新的MBeanServer是监控不到的。原因是JConsole通过Attach API动态attach到已经运行的目标JVM,然后命令其动态的load了JDK_HOME/lib/management-agent.jar这个agent包,这个agent包的agentmain方法会对PlatformMBeanServer进行RMI注册和监听。具体可以参见笔者的另一篇文章

TIPS More example about JMX

其实在官方文档上有很多详细的例子。Java Management Extensions (JMX) Examples

JMX 提供的虚拟机检测API

检测虚拟机当前的状态总是 Java 开放人员所关心的,从 Java SE 5 之后,java.lang.management包里面包括了许多MXBean的接口类和 LockInfo、MemoryUsage、MonitorInfo 和 ThreadInfo 等类。从名字可以看出,该包提供了虚拟机内存分配、垃圾收集(GC)情况、操作系统层、线程调度和共享锁,甚至编译情况的检测机制。

JVM在启动的时候会默认将这些内建的MBean注册到PlatfromMBeanServer。这样一来,Java 的开发人员就可以很简单地为自己做一些轻量级的系统检测,来确定当前程序的各种状态,以便随时调整。jconsole的MBeans中就能够看到这些系统MXBeans。

具体有如下这些MXBean Package java.lang.management:

  • ClassLoadingMXBean: ClassLoadMXBean 包括一些类的装载信息,比如有多少类已经装载/卸载(unloaded),虚拟机类装载的 verbose 选项(即命令行中的 Java – verbose:class 选项)是否打开,还可以帮助用户打开 / 关闭该选项。
  • CompilationMXBean: CompilationMXBean 帮助用户了解当前的编译器和编译情况,该 mxbean 提供的信息不多。
  • GarbageCollectorMXBean: 相对于开放人员对 GC 的关注程度来说,该 mxbean 提供的信息十分有限,仅仅提供了 GC 的次数和 GC 花费总时间的近似值。但是这个包中还提供了三个的内存管理检测类:MemoryManagerMXBean,MemoryMXBean 和 MemoryPoolMXBean。
  • MemoryManagerMXBean: 这个类相对简单,提供了内存管理类和内存池(memory pool)的名字信息。
  • MemoryMXBean: 这个类提供了整个虚拟机中内存的使用情况,包括 Java 堆(heap)和非 Java 堆所占用的内存,提供当前等待 finalize 的对象数量,它甚至可以做 gc(实际上是调用 System.gc)。
  • MemoryPoolMXBean: 该信息提供了大量的信息。在 JVM 中,可能有几个内存池,因此有对应的内存池信息,因此,在工厂类中,getMemoryPoolMXBean() 得到是一个 MemoryPoolMXBean 的 list。每一个 MemoryPoolMXBean 都包含了该内存池的详细信息,如是否可用、当前已使用内存 / 最大使用内存值、以及设置最大内存值等等。
  • OperatingSystemMXBean: 该类提供的是操作系统的简单信息,如构架名称、当前 CPU 数、最近系统负载等。
  • RuntimeMXBean: 运行时信息包括当前虚拟机的名称、提供商、版本号,以及 classpath、bootclasspath 和系统参数等等。
  • ThreadMXBean: 在 Java 这个多线程的系统中,对线程的监控是相当重要的。ThreadMXBean 就是起到这个作用。ThreadMXBean 可以提供的信息包括各个线程的各种状态,CPU 占用情况,以及整个系统中的线程状况。从 ThreadMXBean 可以得到某一个线程的 ThreadInfo 对象。这个对象中则包含了这个线程的所有信息。

如果我们要想获得这些信息,只需要简单的通过java.lang.management.ManagementFactory这个工厂类(单例)来获得相应的的MXBean,然后就可以通过这个MBean获取其监控的数据了:

RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
// Get the standard attribute "VmVendor"
String vendor = mxbean.getVmVendor();

// Or by calling the getPlatformMXBean or getPlatformMXBeans method:
   	RuntimeMXBean mxbean = ManagementFactory.getPlatformMXBean(RuntimeMXBean.class);
   	// Get the standard attribute "VmVendor"
   	String vendor = mxbean.getVmVendor();

或者Construct an MXBean proxy instance that forwards the method calls to a given MBeanServer:

MBeanServerConnection mbs; // Connect to a running JVM (or itself) and get MBeanServerConnection // that has the JVM MBeans registered in it … // Get a MBean proxy for RuntimeMXBean interface RuntimeMXBean proxy = ManagementFactory.getPlatformMXBean(mbs, RuntimeMXBean.class); // Get standard attribute “VmVendor” String vendor = proxy.getVmVendor();

Tomcat有个Tomcat Manager工程就是通过JMXProxyServlet暴露JMX监控项的。它的实现其实非常简单,就是根据用户指定的MBeanName和Attribute,在platformMBeanServer中访问该MBean的属性而已。

核心代码就是这几个函数

public void getAttribute(PrintWriter writer, String onameStr, String att) {
    try {
        ObjectName oname = new ObjectName(onameStr);
        Object value = mBeanServer.getAttribute(oname, att);
        writer.println("OK - Attribute get '" + onameStr + "' - " + att
                + "= " + escape(value.toString()));
    } catch (Exception ex) {
        writer.println("Error - " + ex.toString());
    }
}


public void setAttribute( PrintWriter writer,
                          String onameStr, String att, String val )
{
    try {
        ObjectName oname=new ObjectName( onameStr );
        String type=registry.getType(oname, att);
        Object valueObj=registry.convertValue(type, val );
        mBeanServer.setAttribute( oname, new Attribute(att, valueObj));
        writer.println("OK - Attribute set");
    } catch( Exception ex ) {
        writer.println("Error - " + ex.toString());
    }
}

public void listBeans( PrintWriter writer, String qry )
{

    Set names = null;
    try {
        names=mBeanServer.queryNames(new ObjectName(qry), null);
        writer.println("OK - Number of results: " + names.size());
        writer.println();
    } catch (Exception e) {
        writer.println("Error - " + e.toString());
        return;
    }

    Iterator it=names.iterator();
    while( it.hasNext()) {
        ObjectName oname=(ObjectName)it.next();
        writer.println( "Name: " + oname.toString());

        try {
            MBeanInfo minfo=mBeanServer.getMBeanInfo(oname);
            // can't be null - I thinl
            String code=minfo.getClassName();
            if ("org.apache.commons.modeler.BaseModelMBean".equals(code)) {
                code=(String)mBeanServer.getAttribute(oname, "modelerType");
            }
            writer.println("modelerType: " + code);

            MBeanAttributeInfo attrs[]=minfo.getAttributes();
            Object value=null;

            for( int i=0; i< attrs.length; i++ ) {
                if( ! attrs[i].isReadable() ) continue;
                if( ! isSupported( attrs[i].getType() )) continue;
                String attName=attrs[i].getName();
                if( attName.indexOf( "=") >=0 ||
                        attName.indexOf( ":") >=0 ||
                        attName.indexOf( " ") >=0 ) {
                    continue;
                }
        
                try {
                    value=mBeanServer.getAttribute(oname, attName);
                } catch( Throwable t) {
                    log("Error getting attribute " + oname +
                        " " + attName + " " + t.toString());
                    continue;
                }
                if( value==null ) continue;
                if( "modelerType".equals( attName)) continue;
                String valueString=value.toString();
                writer.println( attName + ": " + escape(valueString));
            }
        } catch (Exception e) {
            // Ignore
        }
        writer.println();
    }
}

JMXProxyServlet的作用是将MBean数据以HTTP方式而不是RMI方式暴露出去。对于页面展示也好,或者穿越防火墙来说都是比较友好的。

参考文档

  1. JMX: Getting Started With MBeans
  2. Java JMX tutorial - A “Hello world” JMX application