天天看點

JMX 與系統監控管理

前言

在 Java 程式的運作過程中,對 JVM 和系統的監測一直是 Java 開發人員在開發過程所需要的。一直以來,Java 開發人員必須通過一些底層的 JVM API,比如 JVMPI 和 JVMTI 等,才能監測 Java 程式運作過程中的 JVM 和系統的一系列情況,這種方式一直以來被人所诟病,因為這需要大量的 C 程式和 JNI 調用,開發效率十分低下。于是出現了各種不同的專門做資源管理的程式包。為了解決這個問題,Sun 公司也在其 Java SE 5 版本中,正式提出了 Java 管理擴充(Java Management Extensions,JMX)用來管理檢測 Java 程式(同時 JMX 也在 J2EE 1.4 中被釋出)。

JMX 的提出,讓 JDK 中開發自檢測程式成為可能,也提供了大量輕量級的檢測 JVM 和運作中對象 / 線程的方式,進而提高了 Java 語言自己的管理監測能力。

回頁首

JMX 和系統管理

管理系統(Management System)

要了解 JMX,我們就必須對目前的 IT 管理系統有一個初步的了解。随着企業 IT 規模的不斷增長,IT 資源(IT resource)數量不斷增加,IT 資源的分布也越來越分散。可以想象,甚至對于一家隻有幾百台 PC 公司的 IT 管理人員來說,分發一個安全更新檔并且保證其在每台 PC 上的安裝,如果隻依賴人工來完成那簡直就是一場噩夢。這樣,IT 管理系統就應運而生。

然而,CPU、網卡、存儲陣列是 IT 資源;OS、MS Office、Oracle database、IBM Websphere 也是 IT 資源。IT 管理系統若要對這些 IT 資源進行管理,就必須對這些管理對象有所了解:形形色色的 IT 資源就像是說着不同語言的人:Oralce 資料庫表達記憶體緊張的方式和 Window XP 是絕然不同的, 而 IT 管理系統就像建造通天塔的經理,必須精通所有的語言, 這幾乎是一個不可能完成的任務。難道 IT 管理系統是另外一個通天塔嗎?當然不是!其實我們隻要給每個 IT 資源配個翻譯就可以了。

管理系統的構架

圖 1. 管理系統構架

JMX 與系統監控管理

上圖分析了管理系統的基本構架模式。其中 Agent / SubAgent 起到的就是翻譯的作用:把 IT 資源報告的消息以管理系統能了解的方式傳送出去。

也許讀者有會問,為什麼需要 Agent 和 SubAgent 兩層體系呢?這裡有兩個現實的原因:

  1. 管理系統一般是一個中央控制的控制軟體,而 SubAgent 直接監控一些資源,往往和這些資源分布在同一實體位置。當這些 SubAgent 把狀态資訊傳輸到管理系統或者傳達管理系統的控制指令的時候,需要提供一些網絡傳輸的功能。
  2. 管理系統的消息是有一定規範的,消息的翻譯本身是件複雜而枯燥的事情。

一般來說,管理系統會将同一實體分布或者功能類似的 SubAgent 分組成一組,由一個共用的 Agent 加以管理。在這個 Agent 裡封裝了 1 和 2 的功能。

JMX 和管理系統

JMX 既是 Java 管理系統的一個标準,一個規範,也是一個接口,一個架構。圖 2 展示了 JMX 的基本架構。

圖 2. JMX 構架

JMX 與系統監控管理

和其它的資源系統一樣,JMX 是管理系統和資源之間的一個接口,它定義了管理系統和資源之間互動的标準。

javax.management.MBeanServer

實作了 Agent 的功能,以标準的方式給出了管理系統通路 JMX 架構的接口。而

javax.management.MBeans

實作了 SubAgent 的功能,以标準的方式給出了 JMX 架構通路資源的接口。而從類庫的層次上看,JMX 包括了核心類庫

java.lang.management

javax.management

包。

java.lang.management

包提供了基本的 VM 監控功能,而

javax.management

包則向使用者提供了擴充功能。

回頁首

JMX 的基本架構

JMX 使用了 Java Bean 模式來傳遞資訊。一般說來,JMX 使用有名的 MBean,其内部包含了資料資訊,這些資訊可能是:應用程式配置資訊、子產品資訊、系統資訊、統計資訊等。另外,MBean 也可以設立可讀寫的屬性、直接操作某些函數甚至啟動 MBean 可發送的 notification 等。MBean 包括 Standard,MXBean,Dynamic,Model,Open 等幾種分類,其中最簡單是标準 MBean 和 MXBean,而我們使用得最多的也是這兩種。MXBean 主要是

java.lang.management

使用較多,将在下一節中介紹。我們先了解其他一些重要的 MBean 的種類。

标準 MBean

标準 MBean 是最簡單的一類 MBean,與動态 Bean 不同,它并不實作

javax.management

包中的特殊的接口。說它是标準 MBean, 是因為其向外部公開其接口的方法和普通的 Java Bean 相同,是通過 lexical,或者說 coding convention 進行的。下面我們就用一個例子來展現,如何實作一個标準 MBean 來監控某個伺服器 ServerImpl 狀态的。ServerImpl 代表了用來示範的某個 Server 的實作:

package standardbeans; 
 public class ServerImpl { 
    public final long startTime; 
    public ServerImpl() { 
        startTime = System.currentTimeMillis(); 
    } 
 } 
      

然後,我們打算使用一個标準 MBean,ServerMonitor 來監控 ServerImpl:

package standardbeans; 
 public class ServerMonitor implements ServerMonitorMBean { 
    private final ServerImpl target; 
    public ServerMonitor(ServerImpl target){ 
        this.target = target; 
    } 
    public long getUpTime(){ 
        return System.currentTimeMillis() - target.startTime; 
    } 
 } 
      

這裡的 ServerMonitorBean 又是怎麼回事呢? MXBean 規定了标準 MBean 也要實作一個接口,所有向外界公開的方法都要在這個接口中聲明。否則,管理系統就不能從中獲得相應的資訊。此外,該接口的名字也有一定的規範:即在标準 MBean 類名之後加上“MBean”字尾。若 MBean 的類名叫做 MBeansName 的話,對應的接口就要叫做 MBeansNameMBean。

對于管理系統來說,這些在 MBean 中公開的方法,最終會被 JMX 轉化成屬性(Attribute)、監聽(Listener)和調用(Invoke)的概念。如果讀者對 Java Bean 有一些了解的話,不難看出,

public long getUpTime()

對應了 Bean 中的一個稱為“upTime”的隻讀屬性。

下面我們就看一個模拟管理系統的例子:

package standardbeans; 
 import javax.management.MBeanServer; 
 import javax.management.MBeanServerFactory; 
 import javax.management.ObjectName; 
 public class Main { 
    private static ObjectName objectName ; 
    private static MBeanServer mBeanServer; 
    public static void main(String[] args) throws Exception{ 
        init(); 
        manage();               
    } 
    private static void init() throws Exception{ 
        ServerImpl serverImpl = new ServerImpl(); 
        ServerMonitor serverMonitor = new ServerMonitor(serverImpl); 
        mBeanServer = MBeanServerFactory.createMBeanServer(); 
        objectName = new ObjectName("objectName:id=ServerMonitor1"); 
        mBeanServer.registerMBean(serverMonitor,objectName);  
    } 
    private static void manage() throws Exception{ 
        Long upTime = (Long) mBeanServer.getAttribute(objectName, 
        "upTime"); 
        System.out.println(upTime); 
    } 
 } 
      

JMX 的核心是 MBServer。Java SE 已經提供了一個預設實作,可以通過

MBServerFactory.createMBeanServer()

獲得。每個資源監控者(MBean)一般都會有名稱(ObjectName), 登記在 MBServer 内部的一個 Repository 中。注意,這個 ObjectName 對于每一個 MBServer 必須是唯一的,隻能對應于一個 MBean。(讀者有興趣的話,可以試着再給 mBeanServer 注冊一個同名的 objectName,看看會怎麼樣。) 上述例子是在

init()

方法中完成向 MBeanServer 注冊工作的。

在管理過程中,管理系統并不與資源或者 SubAgent 直接打交道,也就是說,這裡不會直接引用到 MBean。而是通過 MBeanServer 的

getAttribute

方法取得對應 MBean 的屬性的。

動态 MBean

但是對于很多已有的 SubAgent 實作,其 Coding Convention 并不符合标準 MBean 的要求。重構所有這些 SubAgent 以符合标準 MBean 标準既費力也不實際。JMX 中給出了動态(Dynamic) MBean 的概念,MBServer 不再依據 Coding Convention 而是直接查詢動态 MBean 給出的中繼資料(meta data)以獲得 MBean 的對外接口。

package dynamicbeans; 

 import javax.management.*; 
 import java.lang.reflect.*; 
 public class ServerMonitor implements DynamicMBean { 
 
    private final ServerImpl target;    
    private MBeanInfo mBeanInfo;    
        
    public ServerMonitor(ServerImpl target){ 
        this.target = target; 
    } 
    
    // 實作擷取被管理的 ServerImpl 的 upTime 
    public long upTime(){ 
        return System.currentTimeMillis() - target.startTime; 
    } 

	 //javax.management.MBeanServer 會通過查詢 getAttribute("Uptime") 獲得 "Uptime" 屬性值
    public Object getAttribute(String attribute) throws AttributeNotFoundException, 
		 MBeanException, ReflectionException { 
        if(attribute.equals("UpTime")){ 
            return upTime(); 
        } 
        return null; 
    } 
	
	 // 給出 ServerMonitor 的元資訊。  
    public MBeanInfo getMBeanInfo() { 
        if (mBeanInfo == null) { 
            try { 
                Class cls = this.getClass(); 
                // 用反射獲得 "upTime" 屬性的讀方法
                Method readMethod = cls.getMethod("upTime", new Class[0]); 
                // 用反射獲得構造方法
                Constructor constructor = cls.getConstructor(new Class[] 
					 {ServerImpl.class}); 
                // 關于 "upTime" 屬性的元資訊 : 名稱為 UpTime,隻讀屬性 ( 沒有寫方法 )。
                MBeanAttributeInfo upTimeMBeanAttributeInfo = new MBeanAttributeInfo( 
                        "UpTime", "The time span since server start", 
                        readMethod, null); 
                // 關于構造函數的元資訊
                MBeanConstructorInfo mBeanConstructorInfo = new MBeanConstructorInfo( 
                        "Constructor for ServerMonitor", constructor); 
                //ServerMonitor 的元資訊,為了簡單起見,在這個例子裡,
                // 沒有提供 invocation 以及 listener 方面的元資訊 
                mBeanInfo = new MBeanInfo(cls.getName(), 
                        "Monitor that controls the server", 
                        new MBeanAttributeInfo[] { upTimeMBeanAttributeInfo }, 
                        new MBeanConstructorInfo[] { mBeanConstructorInfo }, 
                        null, null);                
            } catch (Exception e) { 
                throw new Error(e); 
            } 

        } 
        return mBeanInfo; 
    } 

    public AttributeList getAttributes(String[] arg0) {        
        return null; 
    } 
        
    public Object invoke(String arg0, Object[] arg1, String[] arg2) 
		 throws MBeanException, 
		 ReflectionException {        
        return null; 
    } 

    public void setAttribute(Attribute arg0) throws AttributeNotFoundException, 
		 InvalidAttributeValueException, MBeanException, ReflectionException { 
        return;        
    } 

    public AttributeList setAttributes(AttributeList arg0) {        
        return null; 
    }   
 } 
      

其它動态 MBean

另外還有兩類 MBean:Open MBean 和 Model MBean。實際上它們也都是動态 MBean。

Open MBean 與其它動态 MBean 的唯一差別在于,前者對其公開接口的參數和傳回值有所限制 —— 隻能是基本類型或者

javax.management.openmbean

包内的 ArrayType、CompositeType、TarbularType 等類型。這主要是考慮到管理系統的分布,很可能遠端管理系統甚至 MBServer 層都不具有 MBean 接口中特殊的類。

Model Bean

然而,普通的動态 Bean 通常缺乏一些管理系統所需要的支援:比如持久化 MBean 的狀态、日志記錄、緩存等等。如果讓使用者去一一實作這些功能确實是件枯燥無聊的工作。為了減輕使用者的負擔,JMX 提供商都會提供不同的 ModelBean 實作。其中有一個接口是 Java 規範中規定所有廠商必須實作的:

javax.management.modelmbean.RequiredModelBean

。通過配置 Descriptor 資訊,我們可以定制這個 Model Bean, 指定哪些 MBean 狀态需要記入日志、如何記錄以及是否緩存某些屬性、緩存多久等等。這裡,我們以 RequiredModelBean 為例讨論 ModelBean。比如,我們先來看一個例子,首先是 server 端:

package modelmbean; 

 public class Server { 

	 private long startTime; 
	
	 public Server() { 	 } 
	
	 public int start(){ 
		 startTime = System.currentTimeMillis(); 
		 return 0; 
	 } 
	
	 public long getUpTime(){ 
		 return System.currentTimeMillis() - startTime; 
	 } 
 } 
      

然後我們對它的監測如下:

package modelmbean; 

import javax.management.*; 
import javax.management.modelmbean.*; 
public class Main { 

    public static void main(String[] args) throws Exception{ 
        MBeanServer mBeanServer = MBeanServerFactory.createMBeanServer(); 
        RequiredModelMBean serverMBean = 
            (RequiredModelMBean) mBeanServer.instantiate( 
            "javax.management.modelmbean.RequiredModelMBean"); 

        ObjectName serverMBeanName = 
            new ObjectName("server: id=Server"); 
        serverMBean.setModelMBeanInfo(getModelMBeanInfoForServer(serverMBeanName)); 
        Server server = new Server(); 
        serverMBean.setManagedResource(server, "ObjectReference"); 

        ObjectInstance registeredServerMBean = 
            mBeanServer.registerMBean((Object) serverMBean, serverMBeanName); 

        serverMBean.invoke("start",null, null); 

        Thread.sleep(1000); 

        System.out.println(serverMBean.getAttribute("upTime")); 
        Thread.sleep(5000); 
        System.out.println(serverMBean.getAttribute("upTime")); 
    } 

    private static ModelMBeanInfo getModelMBeanInfoForServer(ObjectName objectName) 
        throws Exception{ 
        ModelMBeanAttributeInfo[] serverAttributes = 
            new ModelMBeanAttributeInfo[1]; 
        Descriptor upTime = 
            new DescriptorSupport( 
            new String[] { 
                "name=upTime", 
                "descriptorType=attribute", 
                "displayName=Server upTime", 
                "getMethod=getUpTime",           
            }); 
            serverAttributes[0] = 
                new ModelMBeanAttributeInfo( 
                "upTime", 
                "long", 
                "Server upTime", 
                true, 
                false, 
                false, 
            upTime); 

        ModelMBeanOperationInfo[] serverOperations = 
            new ModelMBeanOperationInfo[2]; 

        Descriptor getUpTimeDesc = 
            new DescriptorSupport( 
            new String[] { 
                "name=getUpTime", 
                "descriptorType=operation", 
                "class=modelmbean.Server", 
                "role=operation"          
            }); 

        MBeanParameterInfo[] getUpTimeParms = new MBeanParameterInfo[0]; 
        serverOperations[0] = new ModelMBeanOperationInfo("getUpTime", 
            "get the up time of the server", 
            getUpTimeParms, 
            "java.lang.Long", 
            MBeanOperationInfo.ACTION, 
        getUpTimeDesc); 
    
        Descriptor startDesc = 
            new DescriptorSupport( 
            new String[] { 
                "name=start", 
                "descriptorType=operation", 
                "class=modelmbean.Server", 
                "role=operation"
            }); 
        MBeanParameterInfo[] startParms = new MBeanParameterInfo[0]; 
        serverOperations[1] = new ModelMBeanOperationInfo("start", 
            "start(): start server", 
            startParms, 
            "java.lang.Integer", 
            MBeanOperationInfo.ACTION, 
        startDesc); 

        ModelMBeanInfo serverMMBeanInfo = 
            new ModelMBeanInfoSupport( 
            "modelmbean.Server", 
            "ModelMBean for managing an Server", 
            serverAttributes, 
            null, 
            serverOperations, 
        null); 

        //Default strategy for the MBean. 
        Descriptor serverDescription = 
            new DescriptorSupport( 
            new String[] { 
                ("name=" + objectName), 
                "descriptorType=mbean", 
                ("displayName=Server"), 
                "type=modelmbean.Server", 
                "log=T", 
                "logFile=serverMX.log", 
                "currencyTimeLimit=10" }); 
        serverMMBeanInfo.setMBeanDescriptor(serverDescription); 
        return serverMMBeanInfo; 
    } 
      

很明顯,和其它 MBean 類似,使用 Model MBean 的過程也是下面幾步:

  1. 建立一個 MBServer:mBeanServe
  2. 獲得管理資源用的 MBean:serverBean
  3. 給這個 MBean 一個 ObjectName:serverMBeanName
  4. 将 serverBean 以 serverMBeanName 注冊到 mBeanServer 上去

唯一不同的是,ModelMBean 需要額外兩步 :

1.serverMBean.setModelMBeanInfo(getModelMBeanInfoForServer(serverMBeanName)); 
 2.serverMBean.setManagedResource(server, "ObjectReference"); 
      

第一步用于提供 serverMBean 的中繼資料,主要包括以下兩類

  1. 類似于普通的動态 MBean,需要 MBean 的 Attribute、Invocation、Notification 的類型 / 反射資訊,諸如傳回類型、參數類型和相關的 get/set 方法等。這裡将不再贅述。
  2. 關于緩存、持久化以及日志等的政策。後面我們将介紹一些這方面的資訊。

第二步指出了 ServerMBean 管理的對象,也就是說,從中繼資料中得到的 Method 将施加在哪個 Object 上。需要指出的是

setManagedResource(Object o, String type);

中第二個參數是 Object 類型,可以是 "ObjectReference"、"Handle"、"IOR"、"EJBHandle" 或 "RMIReference"。目前 SE 中的實作隻支援 "ObjectReference"。筆者認為後面幾種類型是為了将來 JMX 管理對象擴充而設定的,可能将來 Model Bean 不僅可以管理 Plain Java Object(POJO),還可能管理 Native Resource, 并給諸如 EJB 和 RMI 對象的管理提供更多的特性。

Model Bean 與普通動态 Bean 差別在于它的中繼資料類型 ModelMBeanInfo 擴充了前者的 MBeanInfo,使得 ModelMBeanOperationInfo、ModelMBeanConstructor_Info、ModelMBeanAttributeInfo 和 ModelMBeanNotificationInfo 都有一個額外的中繼資料:

javax.management.Descriptor

,它是用來設定 Model Bean 政策的。資料的存儲是典型的 "key-value" 鍵值對。不同的 Model Bean 實作,以及不同的 MBeanFeatureInfo 支援不同的政策特性。下面我們就以 Attribute 為例,看一下 RequiredModelBean 支援的政策。

首先,它最重要的 Descriptor 主要是 name、displayName 和 descriptorType,其中 name 是屬性名稱。"name" 要與對應 ModelMBeanAttributeInfo 的 name 相同。descriptorType 必須是 "attribute"。

另外,value、default、legalValues "value" 是用來設定初始值的,"default" 指當不能從 resource 中獲得該屬性時的預設傳回值,"legalValues" 是一組合法的屬性資料。它并不用來保證 setAttribute 的資料一緻性,而是在 UI 系統,如 JConsole 中提示使用者可能的資料輸入。

在屬性通路的 getMethod, setMethod 方法上,事實上所有對屬性的通路都會被 delegate 給同一 MBeanInfo 中特定的 Operation。 getMethod/setMethod 給出了對應的 ModelMBeanOperationInfo 名稱。

還有一些額外的屬性,比如:persistPolicy, persistPeriod 是代表了持久化政策;currencyTimeLimit, lastUpdatedTimeStamp 緩存政策;iterable 屬性是否必須使用 iterate 來通路。預設為否;protocolMap 定義了與第三方系統有關的資料轉換的 data model;visibility 定義了與第三方 UI 系統有關的 MBean 如何顯示的政策;presentationString 也是定義了與第三方 UI 系統有關的 MBean 如何顯示政策,比如 "presentation=server.gif"。

事實上,政策特性有兩個層次的作用域:整個 Model Bean 和特定的 MBeanFeature。

Model Bean 的政策描述會被施加到該 Model Bean 的所有 MBeanFeature 上去,除非該 MBeanFeature 重寫了這個政策特性。

在上面的例子裡,這一個語句:

serverMMBeanInfo.setMBeanDescriptor(serverDescription); 
	      

給整個 serverMBeanInfo 設了一個政策描述 serverDescription,其中用 "currencyTimeLimit=10" 指出屬性的緩存時間是 10 秒。是以,在 Main 方法中,兩次 serverMBean.getAttribute("upTime");之間的間隔小于 10 秒就會得到同樣的緩存值。

如果我們不想讓 "upTime" 這個屬性被緩存,我們可以在它的政策描述中加入 "currencyTimeLimit=-1":

Descriptor upTime =    new DescriptorSupport( 
		        new String[] { 
		          "name=upTime", 
		          "descriptorType=attribute", 
		          "displayName=Server upTime", 
		          "getMethod=getUpTime", 
		          "currencyTimeLimit=-1" // 不需要緩存
		           }); 

 Descriptor getUpTimeDesc = 
		      new DescriptorSupport( 
		        new String[] { 
		          "name=getUpTime", 
		          "descriptorType=operation", 
		          "class=modelmbean.Server", 
		          "role=operation"
		          ,"currencyTimeLimit=-1" // 不需要緩存
			  }); 
		       

getUpTimeDesc 也要改動的原因是 RequiredModelBean 會把擷取 upTime 屬性的工作 delegate 給 getUpTime invocation。隻要其中一處使用 MBean 級的緩存政策,就沒法獲得實時 upTime 資料了。

回頁首

虛拟機檢測

JMX 與虛拟機檢測

JMX 的提出,為 Java 虛拟機提供了 Java 層上的檢測機制。J2SE 中,新提出的

java.lang.management

包即是 JMX 在 JDK 的一個應用,它提供了大量的有用的接口,通過 MBean 方式,提供了對 Java 虛拟機和運作時遠端的監控和檢測方式,來幫助使用者來檢測本地或者遠端的虛拟機的運作情況。有了 JMX 之後,我們可以設計一個用戶端,來檢測遠端一個正在運作的虛拟機中的線程數、線程目前的 Stack、記憶體管理、GC 所占用的時間、虛拟機中的對象和目前虛拟機參數等重要的參數和運作時資訊。JMX 另外的一個重要功能是對配置資訊的檢測和再配置。比如,我們可以在遠端檢視和修改目前 JVM 的 verbose 參數,以達到動态管理的目的。甚至,我們可以在遠端指揮 JVM 做一次 GC,這在下文中有詳細介紹。

JMX 提供的虛拟機檢測 API

檢測虛拟機目前的狀态總是 Java 開放人員所關心的,也正是因為如此,出現了大量的 profiler 工具來檢測目前的虛拟機狀态。從 Java SE 5 之後,在 JDK 中,我們有了一些 Java 的虛拟機檢測 API,即

java.lang.management

包。Management 包裡面包括了許多 MXBean 的接口類和 LockInfo、MemoryUsage、MonitorInfo 和 ThreadInfo 等類。從名字可以看出,該包提供了虛拟機記憶體配置設定、垃圾收集(GC)情況、作業系統層、線程排程和共享鎖,甚至編譯情況的檢測機制。這樣一來,Java 的開發人員就可以很簡單地為自己做一些輕量級的系統檢測,來确定目前程式的各種狀态,以便随時調整。

要獲得這些資訊,我們首先通過

java.lang.management.ManagementFactory

這個工廠類來獲得一系列的 MXBean。包括:

  • 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 和虛拟機的關系

我們知道,management 和底層虛拟機的關系是非常緊密的。其實,有一些的是直接依靠虛拟機提供的公開 API 實作的,比如 JVMTI;而另外一些則不然,很大一塊都是由虛拟機底層提供某些不公開的 API / Native Code 提供的。這樣的設計方式,保證了 management 包可以提供足夠的資訊,并且使這些資訊的提供又有足夠的效率;也使 management 包和底層的聯系非常緊密。

回頁首

Java 6 中的 API 改進

Management 在 Java SE 5 被提出之後,受到了歡迎。在 Java 6 當中,這個包提供更多的 API 來更好地提供資訊。

OperatingSystemMXBean. getSystemLoadAverage()

Java 程式通常關注是虛拟機内部的負載、記憶體等狀況,而不考慮整個系統的狀況。但是很多情況下,Java 程式在運作過程中,整個計算機系統的系統負荷情況也會對虛拟機造成一定的影響。随着 Java 的發展,Java 程式已經覆寫了各個行業,這一點也必須得到關注。在以前,利用 Native 代碼來檢測系統負載往往是唯一的選擇,但是在 Java 6 當中,JDK 自己提供了一個輕量級的系統負載檢測 API,即

OperatingSystemMXBean.getSystemLoadAverage()

當然這個 API 事實上僅僅傳回一個對前一分鐘系統負載的簡單的估測。它設計的主要目标是簡單快速地估測目前系統負荷,是以它首先保證了這個 API 的效率是非常高的;也因為如此,這個 API 事實上并不适用于所有的系統。

鎖檢測

我們知道,同步是 Java 語言很重要的一個特性。在 Java SE 中,最主要的同步機制是依靠 synchronize 關鍵字對某一個對象加鎖實作的;在 Java SE 5 之後的版本中,concurrent 包的加入,大大強化了 Java 語言的同步能力,concurrent 提供了很多不同類型的鎖機制可供擴充。是以,要更好地觀測目前的虛拟機狀況和不同線程的運作态,去觀察虛拟機中的各種鎖,以及線程與鎖的關系是非常必要的。很可惜的是,在過去的 JDK 中,我們并沒有非常友善的 API 以供使用。一個比較直接的檢測方式是檢視線程的 stack trace,更為強大全面(但是也更複雜并且效率低下)的方案是得到一個 VM 所有對象的快照并查找之,這些政策的代價都比較大,而且往往需要編寫複雜的 Native 代碼。

JDK 6 裡提供了一些相當簡單的 API 來提供這個服務。首先了解兩個新類,LockInfo 和 MonitorInfo 這兩個類承載了鎖的資訊。LockInfo 可以是任何的 Java 鎖,包括簡單 Java 鎖和

java.util.concurrent

包中所使用的鎖(包括 AbstractOwnableSynchronizer 和 Condition 的實作類 / 子類),而 MonitorInfo 是簡單的 Java 對象所代表的鎖。要檢測一個線程所擁有的鎖和等待的鎖,首先,要得到一個線程的 ThreadInfo,然後可以簡單地調用:

  • getLockedMonitors()

    傳回一個所有目前線程已經掌握的鎖對象的清單。
  • getLockedSynchronizers()

    對于使用 concurrent 包的線程,傳回一個該線程所掌握的“ownable synchronizer”(即 AbstractOwnableSynchronizer 及其子類)所組成的清單。
  • getLockInfo()

    目前線程正在等待的那個鎖對象的資訊就可以知道線程所有的鎖資訊。通過這些鎖資訊,我們很友善的可以知道目前虛拟機的所有線程的鎖資訊。由此,我們還可以推導出更多的資訊。

死鎖檢測

死鎖檢測一直以來是軟體工程師所重視的,顯然一個死鎖的系統永遠是工程師最大的夢魇。Java 程式的死鎖檢測也一直以來是 Java 程式員所頭痛的。為了解決線程間死鎖問題,一般都有預防(代碼實作階段)和死鎖後恢複(運作時)兩種方式。以前 Java 程式員都重視前者,因為在運作态再來檢測和恢複系統是相當麻煩的,缺少許多必要的資訊;但是,對于一些比較複雜的系統,采取後者或者運作時調試死鎖資訊也是非常重要的。由上面所說,現在我們已經可以知道每一個線程所擁有和等待的鎖,是以要計算出目前系統中是否有死鎖的線程也是可行的了。當然,Java 6 裡面也提供了一個 API 來完成這個功能,即:

  • ThreadMXBean.findDeadlockedThreads()

    這個函數的功能就是檢測出目前系統中已經死鎖的線程。當然,這個功能複雜,是以比較費時。基本上僅僅将之用于調試,以便對複雜系統線程調用的改進。

回頁首

未來的發展

JMX 在 Java SE 5/6 中的功能已經相當強大,但是距離 Java 程式開發人員的要求還是有一段距離,是以 Sun 公司已經向 JCP 提出了 JSR 255 (JMX API 2.0 版本)來擴充和進一步發展 JMX,并希望這個 JSR 将在 Java SE 7 中實作。在這個文檔中,新的 JMX 2.0 将着重于:

  • 對 management 模型的優化,并提供更好的支援;加入了比如 annotation 等等的新特性;
  • 對 JMX 定義的優化,在進一步強化 MBean 擴充性好的優點的同時,盡量改變(使用者普遍認為的)MBean 很難實作的缺點;
  • 對非 Java 平台用戶端的支援。這将是一個令人振奮的新特性;

具體的擴充可能包括:

  • 層次性的命名域(Hierarchical namespace);
  • 新的事件服務功能;
  • 對 locales 的新支援;
  • 為 MBean 啟用 annotation 服務;
  • 也可以使用使用者類型的 mapping 了;

可以看到,JMX 的進一步發展主要關注的是可擴充性、動态性和易用性等 Java 使用者非常關注的方面。

回頁首

總結

在 Java SE 5 出現的 JMX 在 Java SE 6 中有了更多的功能和擴充能力,這很好地适應了 Java 語言的發展和使用者的要求,讓 Java 的監測、管理的的功能更加強大。

參考資料

  • 閱讀 Java SE 6 新特性系列文章的完整清單,了解 Java SE 6 其它重要的增強。
  • Java 理論與實踐 : 用 JMX 檢測應用程式(developerWorks,Brian Goetz,2006 年 10 月):這篇實踐性的文章介紹了如何使用 Java SE 5 中 JMX API 來檢測 Web 伺服器和應用程式。
  • 從黑箱到企業系列(developerWorks,Sing Li,2002 年):這是一個分為三部分的系列文章,介紹 Java 管理擴充(Java Management Extension ,JMX)。
  • 擴充 Spring 的 JMX 支援(developerWorks,Claude Duguay,2005 年 11 月):本文介紹了 Spring 1.2 中包括進階的 JMX 內建支援。
  • Java SE 6 文檔:Java SE 6 的規範文檔,可以找到絕大部分新特性的官方說明。
  • NTLM 身份驗證微軟 TechNet 相關介紹 NTLM 的文章
  • RFC 2109、RFC 2965:關于 Cookie 的兩個 RFC