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。
注:
- 一個java程序裡面可以有多個不同名字的mBeanServer ,每個mbs都是一個獨立的容器,用了管理mbean
- 每個mbs都可以註冊多個rmi port,http port等
- platformMBeanServer 是由jvm建立的,並添加了一些系統的mbean,如cpu,記憶體,網路,執行緒等等
1、本機使用
當我們啟動java程序後,經常會使用jps,jinfo,jmap,jstat等jdk自帶的命令去查詢程序的狀態,這其中的原理就是,當java程序啟動後,會建立一個用於本機連線的“localConnectorAddress”放到當前使用者目錄下,當使用jps等連線時,會到當前使用者目錄下取到“localConnectorAddress”並連線。
@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(); } } } }
需要注意的是,上面程式碼需要把tools.jar放到classpath裡面。
上面程式碼有時候取不到本地連線地址,這個時候需要嘗試讓agent載入management-agent.jar,完整程式碼如下:
package cn.myroute.mbean;
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 = "cn.myroute.mbean.mm";
// 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=Echo");
// HtmlAdaptorServer adapter = new HtmlAdaptorServer();
// 建立MBean
Echo mbean = new Echo();
// 在MBeanServer裡註冊MBean, 標識為ObjectName(com.tenpay.jmx:type=Echo)
mbs.registerMBean(mbean, 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訪問
@Test
public void testJmxHtmlAdapter() throws Exception {
String jmxServerName = "cn.myroute.mbean.mm";
// 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=Echo");
// HtmlAdaptorServer adapter = new HtmlAdaptorServer();
// 建立MBean
Echo mbean = new Echo();
// 在MBeanServer裡註冊MBean, 標識為ObjectName(com.tenpay.jmx:type=Echo)
mbs.registerMBean(mbean, 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,需要下載jjmx-1_2_1-ri.zip,把裡面的jmxtools.jar加的classpath裡面,下載地址:http://java.sun.com/products/JavaManagement/download.html。 然後用瀏覽器訪問即可
3、客戶端連線
@Test
public void test1() {
try {
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:2222/cn.myroute.mbean.mm");
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();
}
}
4、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 都包含了該記憶體池的詳細資訊,如是否可用、當前已使用記憶體 / 最大使用記憶體值、以及設定最大記憶體值等等。
- MemoryManagerMXBean
- 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());