[Java性能剖析]JVM Management API

JVM本身提供了一组管理的API,通过该API,我们可以获取得到JVM内部主要运行信息,包括内存各代的数据、JVM当前所有线程及其栈相关信息等等。各种JDK自带的剖析工具,包括jps、jstack、jinfo、jstat、jmap、jconsole等,都是基于此API开发的。本篇对这部分内容进行一个详细的说明。

参考:http://java.sun.com/javase/6/docs/api/java/lang/management/package-summary.html
一、Management API
我们先看一下从Sun JVM我们可以获取到哪些信息,如下图(来自于JConsole的MBean部分的截图):

1.HotSpotDiagnostic:非标准的监控JMX,这块是Sun JVM自带的,主要提供了两个功能

  • 修改JVM的启动参数(譬如在不需要重启的情况下设置-XX:+HeapDumpOnOutOfMemoryError参数使得JVM内存不足的时候自动dump出堆空间到文件提供后续分析)
  • Dump堆信息到文件,可以猜测jmap工具是基于此功能来完成的

我们通过com.sun.management.HotSpotDiagnosticMXBean定义了解其主要功能

Java代码  收藏代码
  1. public interface HotSpotDiagnosticMXBean
  2. {
  3.     void dumpHeap(String s, boolean flag) throws IOException;
  4.     List getDiagnosticOptions();
  5.     VMOption getVMOption(String s);
  6.     void setVMOption(String s, String s1);
  7. }

2.ClassLoading:加载的类的总体信息,我们可以通过此MBean获取到JVM加载的类定义的总体信息,可以猜测JConsole的类功能就是通过此MBean来提供的。我们可以通过java.lang.management.ClassLoadingMXBean定义了解其提供的主要功能

Java代码  收藏代码
  1. public interface ClassLoadingMXBean {
  2.     public long getTotalLoadedClassCount();
  3.     public int getLoadedClassCount();
  4.     public long getUnloadedClassCount();
  5.     public boolean isVerbose();
  6.     public void setVerbose(boolean value);
  7. }

3.Compilation:提供JVM的JIT(Just In Time)编译器(将bytecode编译成native code)的信息,我们可以通过java.lang.management.CompilationMXBean定义了解其提供的主要功能

Java代码  收藏代码
  1. public interface CompilationMXBean {
  2.     public java.lang.String    getName();
  3.     public boolean isCompilationTimeMonitoringSupported();
  4.     public long                getTotalCompilationTime();
  5. }

4.GarbageCollector:垃圾回收器信息,譬如在如上图中,我们启动的JVM会包含一个Copy垃圾回收器(用于Young Gen垃圾回收)和一个MarkAndSweep垃圾回收器(用于Tenured Gen垃圾回收)。我们可以通过java.lang.management.GarbageCollectorMXBean定义了解其提供的主要功能

Java代码  收藏代码
  1. public interface GarbageCollectorMXBean extends MemoryManagerMXBean {
  2.     public long getCollectionCount();
  3.     public long getCollectionTime();
  4. }

java.lang.management.MemoryManagerMXBean定义是

Java代码  收藏代码
  1. public interface MemoryManagerMXBean {
  2.     public String getName();
  3.     public boolean isValid();
  4.     public String[] getMemoryPoolNames();
  5. }

除了如上信息,Sun JVM在实现上还提供了一个额外的信息LastGCInfo,见com.sun.management.GarbageCollectorMXBean定义

Java代码  收藏代码
  1. public interface GarbageCollectorMXBean
  2.     extends java.lang.management.GarbageCollectorMXBean
  3. {
  4.     GcInfo getLastGcInfo();
  5. }

我们可以通过下面的截图了解GcInfo包含的主要信息

其中java.lang.management.MemoryUsage后续可以看说明
5.内存相关
可以猜测,JConsole的内存部分的功能都是通过此部分的相关Bean来完成的。
1)Memory/MemoryManager:内存块相关信息,通过这MBean我们可以获取到内存的总体信息,并可以通过提供的gc操作进行强制gc的功能(System.gc())。我们可以通过java.lang.management.MemoryMXBean和java.lang.management.MemoryManagerMXBean了解其主要提供的功能

Java代码  收藏代码
  1. public interface MemoryMXBean {
  2.     public int getObjectPendingFinalizationCount();
  3.     public MemoryUsage getHeapMemoryUsage();
  4.     public MemoryUsage getNonHeapMemoryUsage();
  5.     public boolean isVerbose();
  6.     public void setVerbose(boolean value);
  7.     public void gc();
  8. }

其中java.lang.management.MemoryUsage我们可以通过下图来了解其提供的主要信息

Java代码  收藏代码
  1. public interface MemoryManagerMXBean {
  2.     public String getName();
  3.     public boolean isValid();
  4.     public String[] getMemoryPoolNames();
  5. }

2)MemoryPool:通过该MBean可以了解JVM各内存块的信息,譬如对于Sun JVM,目前包括Eden Space、Suvivor Space、Tenured Gen、CodeCache、Perm Gen,可以猜测JConsole的内存监控功能就是通过此MBean来做到的。我们可以通过java.lang.management.MemoryPoolMXBean了解其主要提供的功能

Java代码  收藏代码
  1. public interface MemoryPoolMXBean {
  2.     public String getName();
  3.     public MemoryType getType();
  4.     public MemoryUsage getUsage();
  5.     public MemoryUsage getPeakUsage();
  6.     public void resetPeakUsage();
  7.     public boolean isValid();
  8.     public String[] getMemoryManagerNames();
  9.     public long getUsageThreshold();
  10.     public void setUsageThreshold(long threshold);
  11.     public boolean isUsageThresholdExceeded();
  12.     public long getUsageThresholdCount();
  13.     public boolean isUsageThresholdSupported();
  14.     public long getCollectionUsageThreshold();
  15.     public void setCollectionUsageThreshold(long threhsold);
  16.     public boolean isCollectionUsageThresholdExceeded();
  17.     public long getCollectionUsageThresholdCount();
  18.     public MemoryUsage getCollectionUsage();
  19.     public boolean isCollectionUsageThresholdSupported();
  20. }

6.系统运行信息
1)OperatingSystem:通过该MBean我们可以了解到JVM所运行在的操作系统上的一些相关信息,通过java.lang.management.OperatingSystemMXBean定义我们可以了解到其主要提供的功能

Java代码  收藏代码
  1. public interface OperatingSystemMXBean {
  2.     public String getName();
  3.     public String getArch();
  4.     public String getVersion();
  5.     public int getAvailableProcessors();
  6.     public double getSystemLoadAverage();
  7. }

SunJVM在此基础上提供更多的一些信息,可以通过com.sun.management.OperatingSystemMXBean了解一些额外可以获取到的信息

Java代码  收藏代码
  1. public interface OperatingSystemMXBean
  2.     extends java.lang.management.OperatingSystemMXBean
  3. {
  4.     long getCommittedVirtualMemorySize();
  5.     long getTotalSwapSpaceSize();
  6.     long getFreeSwapSpaceSize();
  7.     long getProcessCpuTime();
  8.     long getFreePhysicalMemorySize();
  9.     long getTotalPhysicalMemorySize();
  10. }

2)Runtime:通过该MBean获取获取到JVM一些相关的信息,通过java.lang.management.RuntimeMXBean可以了解其主要提供的功能

Java代码  收藏代码
  1. public interface RuntimeMXBean {
  2.     public String getName();
  3.     public String getVmName();
  4.     public String getVmVendor();
  5.     public String getVmVersion();
  6.     public String getSpecName();
  7.     public String getSpecVendor();
  8.     public String getSpecVersion();
  9.     public String getManagementSpecVersion();
  10.     public String getClassPath();
  11.     public String getLibraryPath();
  12.     public boolean isBootClassPathSupported();
  13.     public String getBootClassPath();
  14.     public java.util.List<String> getInputArguments();
  15.     public long getUptime();
  16.     public long getStartTime();
  17.     public java.util.Map<String, String> getSystemProperties();
  18. }

可以通过RuntimeMXBean.getUptime()和OperatingSystemMXBean. getProcessCpuTime()来计算JVM占用的系统CPU比例的情况,JConsole的CPU视图就是通过这种方式计算的。
7.Threading:可以通过该MBean获取线程信息,包括线程状态、执行栈等。可以通过java.lang.management.ThreadMXBean了解其提供的主要功能

Java代码  收藏代码
  1. public interface ThreadMXBean {
  2.    public int getThreadCount();
  3.     public int getPeakThreadCount();
  4.     public long getTotalStartedThreadCount();
  5.     public int getDaemonThreadCount();
  6.     public long[] getAllThreadIds();
  7.     public ThreadInfo getThreadInfo(long id);
  8.     public ThreadInfo[] getThreadInfo(long[] ids);
  9.     public ThreadInfo getThreadInfo(long id, int maxDepth);
  10.     public ThreadInfo[] getThreadInfo(long[] ids, int maxDepth);
  11.     public boolean isThreadContentionMonitoringSupported();
  12.     public boolean isThreadContentionMonitoringEnabled();
  13.     public void setThreadContentionMonitoringEnabled(boolean enable);
  14.     public long getCurrentThreadCpuTime();
  15.     public long getCurrentThreadUserTime();
  16.     public long getThreadCpuTime(long id);
  17.     public long getThreadUserTime(long id);
  18.     public boolean isThreadCpuTimeSupported();
  19.     public boolean isCurrentThreadCpuTimeSupported();
  20.     public boolean isThreadCpuTimeEnabled();
  21.     public void setThreadCpuTimeEnabled(boolean enable);
  22.     public long[] findMonitorDeadlockedThreads();
  23.     public void resetPeakThreadCount();
  24.     public long[] findDeadlockedThreads();
  25.     public boolean isObjectMonitorUsageSupported();
  26.     public boolean isSynchronizerUsageSupported();
  27.     public ThreadInfo[] getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers);
  28.     public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers);
  29. }

二、编程获取到JVM Manage信息
我们可以通过JMX的方式读取到JVM Manage定义的MBean,如下是3种获取方法
1.监控应用与被监控应用位于同一JVM

Java代码  收藏代码
  1. MBeanServer server = ManagementFactory.getPlatformMBeanServer();
  2. RuntimeMXBean rmxb = ManagementFactory.newPlatformMXBeanProxy(server,
  3.                 ”java.lang:type=Runtime”, RuntimeMXBean.class);

2.监控应用与被监控应用不位于同一JVM
1)首先在被监控的JVM的启动参数中加入如下的启动参数以启JVM代理

-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=127.0.0.1:8000 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

2)连接到代理上

Java代码  收藏代码
  1. JMXServiceURL url = new JMXServiceURL(
  2.         ”service:jmx:rmi:///jndi/rmi://127.0.0.1:8000/jmxrmi”);
  3. JMXConnector connector = JMXConnectorFactory.connect(url);
  4. RuntimeMXBean rmxb = ManagementFactory.newPlatformMXBeanProxy(connector
  5.             .getMBeanServerConnection(),”java.lang:type=Runtime”,
  6.                 RuntimeMXBean.class);

3.监控应用与被监控应用不位于同一JVM但在同一物理主机上(2的特化情况,通过进程Attach)
我们使用JDK工具,如jmap、jstack等的时候,工具所在的JVM当然与被监控的JVM不是同一个,所以不能使用方式1,被监控的JVM一般也不会在启动参数中增加JMX的支持,所以方式2也没有办法。还好Sun JVM给我们提供了第3种非标准的方式,就是通过Attach到被监控的JVM进程,并在被监控的JVM中启动一个JMX代理,然后使用该代理通过2的方式连接到被监控的JVM的JMX上。下面是一个使用范例,由于里面使用到的知识涉及到Java Instrutment(JVMTI的一个技术的Java实现)和Attach API,因此此处不做详细解析,在后续看完Java Instrutment和Attach API自然就会明白。(注意,仅在JDK6+中支持,另外,运行需要jdk的tools.jar包)

Java代码
  1. //Attach 到5656的JVM进程上,后续Attach API再讲解
  2. VirtualMachine virtualmachine = VirtualMachine.attach(“5656″);
  3. //让JVM加载jmx Agent,后续讲到Java Instrutment再讲解
  4. String javaHome = virtualmachine.getSystemProperties().getProperty(“java.home”);
  5. String jmxAgent = javaHome + File.separator + ”lib” + File.separator + ”management-agent.jar”;
  6. virtualmachine.loadAgent(jmxAgent, ”com.sun.management.jmxremote”);
  7. //获得连接地址
  8. Properties properties = virtualmachine.getAgentProperties();
  9. String address = (String)properties.get(“com.sun.management.jmxremote.localConnectorAddress”);
  10. //Detach
  11. virtualmachine.detach();
  12. JMXServiceURL url = new JMXServiceURL(address);
  13. JMXConnector connector = JMXConnectorFactory.connect(url);
  14. RuntimeMXBean rmxb = ManagementFactory.newPlatformMXBeanProxy(connector
  15.                 .getMBeanServerConnection(), ”java.lang:type=Runtime”,RuntimeMXBean.class);

再附上一个比较全的demo


import java.lang.management.ClassLoadingMXBean;
import java.lang.management.CompilationMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryManagerMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.List;

import javax.management.MBeanServerConnection;

import org.apache.commons.lang.StringUtils;

/**
 * @author CN14721
 *
 */
public class MBeanDemo {
	public static void main(String[] args) {
		showJvmInfo();
		showMemoryInfo();
		showSystem();
		showClassLoading();
		showCompilation();
		showThread();
		showGarbageCollector();
		showMemoryManager();
		showMemoryPool();
	}

	/** 
	 * Java 虚拟机的运行时系统 
	 */
	public static void showJvmInfo() {
		RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
		String vendor = mxbean.getVmVendor();
		System.out.println("jvm name:" + mxbean.getVmName());
		System.out.println("jvm version:" + mxbean.getVmVersion());
		System.out.println("jvm bootClassPath:" + mxbean.getBootClassPath());
		System.out.println("jvm start time:" + mxbean.getStartTime());
	}

	/** 
	 * Java 虚拟机的内存系统 
	 */
	public static void showMemoryInfo() {
		MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
		MemoryUsage heap = mem.getHeapMemoryUsage();
		System.out.println("Heap committed:" + heap.getCommitted() + " init:" + heap.getInit() + " max:"
			+ heap.getMax() + " used:" + heap.getUsed());
	}

	/** 
	 * Java 虚拟机在其上运行的操作系统 
	 */
	public static void showSystem() {
		OperatingSystemMXBean op = ManagementFactory.getOperatingSystemMXBean();
		System.out.println("Architecture: " + op.getArch());
		System.out.println("Processors: " + op.getAvailableProcessors());
		System.out.println("System name: " + op.getName());
		System.out.println("System version: " + op.getVersion());
		System.out.println("Last minute load: " + op.getSystemLoadAverage());
	}

	/** 
	 * Java 虚拟机的类加载系统 
	 */
	public static void showClassLoading() {
		ClassLoadingMXBean cl = ManagementFactory.getClassLoadingMXBean();
		System.out.println("TotalLoadedClassCount: " + cl.getTotalLoadedClassCount());
		System.out.println("LoadedClassCount" + cl.getLoadedClassCount());
		System.out.println("UnloadedClassCount:" + cl.getUnloadedClassCount());
	}

	/** 
	 * Java 虚拟机的编译系统 
	 */
	public static void showCompilation() {
		CompilationMXBean com = ManagementFactory.getCompilationMXBean();
		System.out.println("TotalCompilationTime:" + com.getTotalCompilationTime());
		System.out.println("name:" + com.getName());
	}

	/** 
	 * Java 虚拟机的线程系统 
	 */
	public static void showThread() {
		ThreadMXBean thread = ManagementFactory.getThreadMXBean();
		System.out.println("ThreadCount:" + thread.getThreadCount());
		long threadIds[] = thread.getAllThreadIds();
		List<Long> threadIdList = new ArrayList<Long>();
		for(long threadId : threadIds){
			threadIdList.add(threadId);
		}
		System.out.println("AllThreadIds:" + StringUtils.join(threadIdList.toArray(), ","));
		System.out.println("CurrentThreadUserTime:" + thread.getCurrentThreadUserTime());
		
		ThreadInfo infos[] = thread.getThreadInfo(thread.getAllThreadIds(), 30);
		for(ThreadInfo info : infos){
			System.out.format("thread id:%d, name:%s, state:%s, waitedCount:%d\n", info.getThreadId(), info.getThreadName(), info.getThreadState(), info.getWaitedTime());
			for(StackTraceElement stk : info.getStackTrace()){
				System.out.format("class:%s,line:%d,method:%s,file:%s\n",stk.getClassName(),stk.getLineNumber(),stk.getMethodName(),stk.getFileName());
			}
		}
		//......还有其他很多信息  
	}

	/** 
	 * Java 虚拟机中的垃圾回收器。 
	 */
	public static void showGarbageCollector() {
		List<GarbageCollectorMXBean> gc = ManagementFactory.getGarbageCollectorMXBeans();
		for (GarbageCollectorMXBean GarbageCollectorMXBean : gc) {
			System.out.println("name:" + GarbageCollectorMXBean.getName());
			System.out.println("CollectionCount:" + GarbageCollectorMXBean.getCollectionCount());
			System.out.println("CollectionTime" + GarbageCollectorMXBean.getCollectionTime());
		}
	}

	/** 
	 * Java 虚拟机中的内存管理器 
	 */
	public static void showMemoryManager() {
		List<MemoryManagerMXBean> mm = ManagementFactory.getMemoryManagerMXBeans();
		for (MemoryManagerMXBean eachmm : mm) {
			System.out.println("name:" + eachmm.getName());
			System.out.println("MemoryPoolNames:" + eachmm.getMemoryPoolNames().toString());
		}
	}

	/** 
	 * Java 虚拟机中的内存池 
	 */
	public static void showMemoryPool() {
		List<MemoryPoolMXBean> mps = ManagementFactory.getMemoryPoolMXBeans();
		for (MemoryPoolMXBean mp : mps) {
			System.out.println("name:" + mp.getName());
			System.out.println("CollectionUsage:" + mp.getCollectionUsage());
			System.out.println("type:" + mp.getType());
		}
	}

	/** 
	 * 访问 MXBean 的方法的三种方法 
	 */
	public static void visitMBean() {

		//第一种直接调用同一 Java 虚拟机内的 MXBean 中的方法。  
		RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
		String vendor1 = mxbean.getVmVendor();
		System.out.println("vendor1:" + vendor1);

		//第二种通过一个连接到正在运行的虚拟机的平台 MBeanServer 的 MBeanServerConnection。  
		MBeanServerConnection mbs = null;
		// Connect to a running JVM (or itself) and get MBeanServerConnection  
		// that has the JVM MXBeans registered in it  

		/* 
		try { 
		    // Assuming the RuntimeMXBean has been registered in mbs 
		    ObjectName oname = new ObjectName(ManagementFactory.RUNTIME_MXBEAN_NAME); 
		    String vendor2 = (String) mbs.getAttribute(oname, "VmVendor"); 
		    System.out.println("vendor2:" + vendor2); 
		} catch (Exception e) { 
		    e.printStackTrace(); 
		} 
		*/

		//第三种使用 MXBean 代理  
		//        MBeanServerConnection mbs3 = null;  
		//        RuntimeMXBean proxy;  
		//        try {  
		//            proxy = ManagementFactory.newPlatformMXBeanProxy(mbs3,ManagementFactory.RUNTIME_MXBEAN_NAME,  
		//                                                     RuntimeMXBean.class);  
		//            String vendor = proxy.getVmVendor();  
		//        } catch (IOException e) {  
		//            e.printStackTrace();  
		//        }  

	}

	
}

 

三、结束语
可以看到,通过标准的接口,我们已经可以获得运行的JVM很详细的信息,从运行JVM、操作系统,到内存、GC和线程,通过这些标准的接口我们已经可以对JVM进行功能完善的监控。但是仅此是不够的,这部分接口描述的主要是JVM的总体性的信息,而无法提供更多的细节。在下一部分,我们将使用JPDA来更深入地了解JVM内部信息更细节的信息,并了解我们如何通过JVM TI实现自动的性能监控

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>