zoukankan      html  css  js  c++  java
  • JMX-JAVA进程监控利器

    Java 管理扩展(Java Management Extension,JMX)是从jdk1.4开始的,但从1.5时才加到jdk里面,并把API放到java.lang.management包里面。

    如果一个 Java 对象可以由一个遵循 JMX 规范的管理器应用管理,那么这个Java 对象就可以称为一个可由 JMX 管理的资源。


    要使一个 Java 对象可管理,则必须创建相应的 MBean 对象,并通过这些 MBean 对象管理相应的 Java 对象。当拥有 MBean 类后,需要将其实例化并注册到 MBeanServer 上。


    一共有四种类型的 MBean , 分别是标准类型 MBean, 动态类型 MBean, 开放类型 MBean 和模型类型 MBean。

    注:

    1. 一个java进程里面可以有多个不同名字的mBeanServer ,每个mbs都是一个独立的容器,用了管理mbean
    2. 每个mbs都可以注册多个rmi port,http port等
    3. platformMBeanServer 是由jvm创建的,并添加了一些系统的mbean,如cpu,内存,网络,线程等等

    1、本机使用

    当我们启动java进程后,经常会使用jps,jinfo,jmap,jstat等jdk自带的命令去查询进程的状态,这其中的原理就是,当java进程启动后,会创建一个用于本机连接的“localConnectorAddress”放到当前用户目录下,当使用jps等连接时,会到当前用户目录下取到“localConnectorAddress”并连接。

    package com.dxz.study;
    
    import java.io.IOException;
    import java.util.List;
    import java.util.Map;
    import java.util.Properties;
    import java.util.Set;
    
    import javax.management.MBeanServerConnection;
    import javax.management.ObjectName;
    import javax.management.remote.JMXConnector;
    import javax.management.remote.JMXConnectorFactory;
    import javax.management.remote.JMXServiceURL;
    
    import org.junit.Test;
    
    import com.sun.tools.attach.VirtualMachine;
    import com.sun.tools.attach.VirtualMachineDescriptor;
    
    public class JmxTest {
    
        @Test  
        public void test1() {  
            List<VirtualMachineDescriptor> vms = VirtualMachine.list();  
            for (VirtualMachineDescriptor desc : vms) {  
                VirtualMachine vm;  
                try {  
                    System.out.println("desc:" + desc);  
                    System.out.println("进程id:"+desc.id());  
                    vm = VirtualMachine.attach(desc);  
                } catch (Exception e) {  
                    e.printStackTrace();  
                    continue;  
                }  
                JMXConnector connector = null;  
                try {  
                    Properties props = vm.getAgentProperties();  
                    for (Map.Entry<Object, Object> entry : props.entrySet()) {  
                        System.out.println(entry.getKey() + "->" + entry.getValue());  
                    }                 
                      
                    String connectorAddress = props.getProperty("com.sun.management.jmxremote.localConnectorAddress");  
                    if (connectorAddress == null) {  
                        System.out.println("connectorAddress  is  null");  
                        continue;  
                    }  
                    System.out.println("conn:" + connectorAddress);  
                     //以下代码用于连接指定的jmx,本地或者远程  
                    JMXServiceURL url = new JMXServiceURL(connectorAddress);  
                //JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/TestJMXServer");   
                    connector = JMXConnectorFactory.connect(url);  
          
                    MBeanServerConnection mbeanConn = connector.getMBeanServerConnection();  
                    Set<ObjectName> beanSet = mbeanConn.queryNames(null, null);  
                    // ...  
                } catch (Exception e) {  
                    e.printStackTrace();  
                } finally {  
                    try {  
                        if (connector != null) connector.close();  
                        break;  
                    } catch (IOException e) {  
                        // TODO Auto-generated catch block  
                        e.printStackTrace();  
                    }  
                }  
            }  
        }  
    }

    pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <groupId>com.dxz</groupId>
      <artifactId>study</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <packaging>jar</packaging>
    
      <name>study</name>
      <url>http://maven.apache.org</url>
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.1</version>
          <scope>test</scope>
        </dependency>
        <dependency>
         <groupId>org.glassfish.external</groupId>
        <artifactId>opendmk_jdmkrt_jar</artifactId>
        <version>1.0-b01-ea</version>
        </dependency>
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.24</version>
        </dependency>
      </dependencies>
    </project>

    上面代码有时候取不到本地连接地址,这个时候需要尝试让agent加载management-agent.jar,完整代码如下:

    package com.dxz.study;
    
    import java.io.File;
    import java.io.IOException;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.util.List;
    import java.util.Properties;
    
    public class AbstractJmxCommand {
        private static final String CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress";
    
        public static String getJVM() {
            return System.getProperty("java.vm.specification.vendor");
        }
    
        public static boolean isSunJVM() {
            // need to check for Oracle as that is the name for Java7 onwards.
            return getJVM().equals("Sun Microsystems Inc.") || getJVM().startsWith("Oracle");
        }
    
        public static void main(String[] args) {
            if (args == null || args.length == 0) {
                System.out.println("Usage: pid");
                return;
            }
            int pid = Integer.valueOf(args[0]);
            System.out.println(new AbstractJmxCommand().findJMXUrlByProcessId(pid));
    
        }
    
        /**
         * Finds the JMX Url for a VM by its process id
         * 
         * @param pid
         *            The process id value of the VM to search for.
         * 
         * @return the JMX Url of the VM with the given pid or null if not found.
         */
        // @SuppressWarnings({ "rawtypes", "unchecked" })
        protected String findJMXUrlByProcessId(int pid) {
    
            if (isSunJVM()) {
                try {
                    // Classes are all dynamically loaded, since they are specific
                    // to Sun VM
                    // if it fails for any reason default jmx url will be used
    
                    // tools.jar are not always included used by default class
                    // loader, so we
                    // will try to use custom loader that will try to load tools.jar
    
                    String javaHome = System.getProperty("java.home");
                    String tools = javaHome + File.separator + ".." + File.separator + "lib" + File.separator + "tools.jar";
                    URLClassLoader loader = new URLClassLoader(new URL[] { new File(tools).toURI().toURL() });
    
                    Class virtualMachine = Class.forName("com.sun.tools.attach.VirtualMachine", true, loader);
                    Class virtualMachineDescriptor = Class.forName("com.sun.tools.attach.VirtualMachineDescriptor", true,
                            loader);
    
                    Method getVMList = virtualMachine.getMethod("list", (Class[]) null);
                    Method attachToVM = virtualMachine.getMethod("attach", String.class);
                    Method getAgentProperties = virtualMachine.getMethod("getAgentProperties", (Class[]) null);
                    Method getVMId = virtualMachineDescriptor.getMethod("id", (Class[]) null);
    
                    List allVMs = (List) getVMList.invoke(null, (Object[]) null);
    
                    for (Object vmInstance : allVMs) {
                        String id = (String) getVMId.invoke(vmInstance, (Object[]) null);
                        if (id.equals(Integer.toString(pid))) {
    
                            Object vm = attachToVM.invoke(null, id);
    
                            Properties agentProperties = (Properties) getAgentProperties.invoke(vm, (Object[]) null);
                            String connectorAddress = agentProperties.getProperty(CONNECTOR_ADDRESS);
    
                            if (connectorAddress != null) {
                                return connectorAddress;
                            } else {
                                break;
                            }
                        }
                    }
    
                    // 上面的尝试都不成功,则尝试让agent加载management-agent.jar
                    Method getSystemProperties = virtualMachine.getMethod("getSystemProperties", (Class[]) null);
                    Method loadAgent = virtualMachine.getMethod("loadAgent", String.class, String.class);
                    Method detach = virtualMachine.getMethod("detach", (Class[]) null);
                    for (Object vmInstance : allVMs) {
                        String id = (String) getVMId.invoke(vmInstance, (Object[]) null);
                        if (id.equals(Integer.toString(pid))) {
    
                            Object vm = attachToVM.invoke(null, id);
    
                            Properties systemProperties = (Properties) getSystemProperties.invoke(vm, (Object[]) null);
                            String home = systemProperties.getProperty("java.home");
    
                            // Normally in ${java.home}/jre/lib/management-agent.jar
                            // but might
                            // be in ${java.home}/lib in build environments.
    
                            String agent = home + File.separator + "jre" + File.separator + "lib" + File.separator
                                    + "management-agent.jar";
                            File f = new File(agent);
                            if (!f.exists()) {
                                agent = home + File.separator + "lib" + File.separator + "management-agent.jar";
                                f = new File(agent);
                                if (!f.exists()) {
                                    throw new IOException("Management agent not found");
                                }
                            }
    
                            agent = f.getCanonicalPath();
    
                            loadAgent.invoke(vm, agent, "com.sun.management.jmxremote");
    
                            Properties agentProperties = (Properties) getAgentProperties.invoke(vm, (Object[]) null);
                            String connectorAddress = agentProperties.getProperty(CONNECTOR_ADDRESS);
    
                            // detach 这个vm
                            detach.invoke(vm, (Object[]) null);
    
                            if (connectorAddress != null) {
                                return connectorAddress;
                            } else {
                                break;
                            }
                        }
                    }
                } catch (Exception ignore) {
                    ignore.printStackTrace();
                }
            }
    
            return null;
        }
    }

    2、远程连接

    毫无疑问,若想远程连接访问,肯定需要mBeanServer注册一个或多个端口,如rmi端口,http端口等。
     

    2.1 rmi端口注册及访问

    有两种方法,一种直接在代码里面指定rmi端口,并绑定,如下,此种方法需要使用客户端连接代码访问,另一种代码不用指定端口,之需把mbean注册到platformMBeanServer 里面,并在启动进程时加jmx参数指定,用这种方法可以通过jconsole,jvisualvm远程访问。

    2.1.1 直接在代码里面绑定端口

    @Test  
        public void testJmxRmiRegist() throws Exception {  
            int rmiPort = 2222;  
            String jmxServerName = "com.dxz.study.TestJmxRmiRegist";  
      
            // jdkfolder/bin/rmiregistry.exe 9999  
            Registry registry = LocateRegistry.createRegistry(rmiPort);  
      
            MBeanServer mbs = MBeanServerFactory.createMBeanServer(jmxServerName);  
            System.out.println(mbs);  
            // mbs = MBeanServerFactory.createMBeanServer();  
            // 新建MBean ObjectName, 在MBeanServer里标识注册的MBean  
            ObjectName name = new ObjectName(jmxServerName + ":type=HelloWorld");  
            // HtmlAdaptorServer adapter = new HtmlAdaptorServer();  
      
            // 在MBeanServer里注册MBean, 标识为ObjectName(com.tenpay.jmx:type=Echo)  
            mbs.registerMBean(new HelloWorld(), name);  
      
            JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + rmiPort + "/" + jmxServerName);  
            System.out.println("JMXServiceURL: " + url.toString());  
            JMXConnectorServer jmxConnServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);  
            jmxConnServer.start();  
      
            Thread.sleep(1000 * 60 * 10);  
        } 
    上面程序是新建了个mbeanserver,并通过rmi绑定到2222端口上,等待客户端连接。
     

    2.1.2 通过jmx参数启动进程

    #JVMARGS="$JVMARGS -Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
     
    通过这种把进程的jmx监控绑定指定的端口,即可在远端通过jconsole进行监控。
     

    2.2通过http访问

    1. @Test  
          public void testJmxHtmlAdapter() throws Exception {  
              String jmxServerName = "com.dxz.study.TestJmxRmiRegist"; 
                
              // jdkfolder/bin/rmiregistry.exe 9999  
                
              MBeanServer mbs = MBeanServerFactory.createMBeanServer(jmxServerName);  
              System.out.println(mbs);  
              // mbs = MBeanServerFactory.createMBeanServer();  
              // 新建MBean ObjectName, 在MBeanServer里标识注册的MBean  
              ObjectName name = new ObjectName(jmxServerName + ":type=HelloWorld");  
              // HtmlAdaptorServer adapter = new HtmlAdaptorServer();  
              // 创建MBean  
              // 在MBeanServer里注册MBean, 标识为ObjectName(com.tenpay.jmx:type=Echo)  
              mbs.registerMBean(new HelloWorld(), name);  
               HtmlAdaptorServer adapter = new HtmlAdaptorServer();    
                      ObjectName adapterName;    
                       adapterName = new ObjectName(jmxServerName + ":name=" + "htmladapter");    
                       adapter.setPort(8082);    
                      adapter.start();    
                     mbs.registerMBean(adapter, adapterName);    
                
              Thread.sleep(1000 * 60 * 10);  
          }
    以上代码用到了HtmlAdaptorServer,
    <dependency>
         <groupId>org.glassfish.external</groupId>
        <artifactId>opendmk_jdmkrt_jar</artifactId>
        <version>1.0-b01-ea</version>
        </dependency>
    然后用浏览器访问即可
     

    3、客户端连接

    package com.dxz.study;
    
    import java.util.Set;
    
    import javax.management.MBeanServerConnection;
    import javax.management.ObjectName;
    import javax.management.remote.JMXConnector;
    import javax.management.remote.JMXConnectorFactory;
    import javax.management.remote.JMXServiceURL;
    
    import org.junit.Test;
    
    public class JmxClientTest {
        @Test  
        public void test1() {  
            try {  
                JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:2222/com.dxz.study.TestJmxRmiRegist");  
                JMXConnector connector = JMXConnectorFactory.connect(url);  
      
                MBeanServerConnection mbeanConn = connector.getMBeanServerConnection();  
                Set<ObjectName> beanSet = mbeanConn.queryNames(null, null);  
                System.out.println(beanSet);  
            }catch (Exception e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
    }

    结果:

    [com.dxz.study.TestJmxRmiRegist:type=HelloWorld, JMImplementation:type=MBeanServerDelegate]

    4、jconsole连接(待验证)

    5、java进程自带的mbean

    当我们在用jconsole、jvisualvm进行监控java进程时,通常都能看到cpu、内存、线程、垃圾收集等使用情况,其实数据都是通过jmx从jvm提供的一些mbean里面取的。主要如下:
    • 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。
    ClassLoadingMXBean mbs = ManagementFactory.getClassLoadingMXBean();  
    System.out.println("loadedClass:" + mbs.getLoadedClassCount()); 
  • 相关阅读:
    C面试复习笔记
    Java面试复习笔记
    Jdk1.6 HTTPS访问问题解决办法
    百度地图轨迹回放,自定义路书,边走边画线
    简单的代码生成小工具(支持模板)
    card布局解决复杂操作的布局问题
    tabpanel如何隐藏页签表头以及基本用法总结
    ExtJS4.2下将表单元素放在菜单时不能进行拷贝的问题解决办法
    照片元数据信息以及在照片中写入gps信息
    带名称空间的xml数据查询
  • 原文地址:https://www.cnblogs.com/duanxz/p/4474750.html
Copyright © 2011-2022 走看看