JMX初體驗

 

 

 

JMX

在 Java 5.0 中,類庫和和 JVM 提供了一種全面的管理和監視基礎設施——JMX。JMX 是一種用來提供可以遠程訪問的管理接口的標準措施,也是一種嚮應用程序添加靈活且強大的管理接口的簡易方式。被稱作受管 bean(MBean)的 JMX 組件,是提供與實體的管理有關的訪問器和業務方法的 JavaBean。每個受管的實體(可能是整個應用程序或應用程序中的服務)實例化一個 MBean 並用可讀懂的名稱註冊它。支持 JMX 的應用程序依賴於 MBeanServer,它充當 MBean 的容器,提供遠程訪問、命名空間管理和安全服務。在客戶端,jconsole 工具可以充當統一的 JMX 客戶機。結合兩者,對 JMX 的平臺支持極大地降低了使應用程序支持外部管理接口所需的工作和努力。

除了提供 MBeanServer 實現,Java SE 5.0 還提供 JVM 以更方便地瞭解內存管理、類裝入、活動線程、日誌和平臺配置的狀態。多數平臺服務的監視和管理在默認情況下都是開啓的(性能影響最小),所以只需要連接應用程序與 JMX 客戶機即可。圖 1 給出了 jconsole JMX 客戶機(JDK 的一部分) ,它顯示了其中一個內存管理視圖——一段時間內的堆使用情況。Perform GC 按鈕則證明了 JMX 可以提供 除了查看操作統計值之外的初始化操作的功能。


圖 1. 用 jconsole 查看堆使用情況
 

傳輸和安全性

JMX 指定了在 MBeanServer 和 JMX 客戶之間通信所使用的協議,協議可以在各種傳輸機制上運行。可以使用針對本地連接的內置傳輸,及通過 RMI、socket 或 SSL 的遠程傳輸(可以通過 JMX Connector API 創建新的傳輸)。認證是由傳輸執行的;本地傳輸允許用相同的用戶 ID 連接到運行在本地系統上的 JVM;遠程傳輸可以用口令或證書進行認證。本地傳輸在 Java 6 下默認就是啓用的。要在 Java 5.0 下啓用它,需要在 JVM 啓動時定義系統屬性 com.sun.management.jmxremote。“Monitoring and Management using JMX” 這份文檔(請參閱參考資料)描述了啓用和配置傳輸的配置步驟。

檢測 Web 服務器

檢測應用程序來使用 JMX 很容易。像其他許多遠程調用框架(RMI、EJB 和 JAX-RPC)一樣,JMX 也是基於接口的。要創建管理服務,需要創建指定管理方法的 MBean 接口。然後可以創建一個 MBean 來實現此接口、實例化它及把它註冊到MBeanServer

清單 1 顯示了網絡服務(例如 Web 服務器)的 MBean 接口。它提供了檢索配置信息(例如端口號)和操作性信息(例如服務是否啓動)的 getter。它還包含查看和修改可配置參數(例如當前日誌級別)的 getter 和 setter,還有調用管理操作(例如start()  stop())的方法。


清單 1. 某個 Web 服務器的 MBean 接口

public interface WebServerMBean {
    public int getPort();

    public String getLogLevel();
    public void setLogLevel(String level);

    public boolean isStarted();
    public void stop();
    public void start();
}

 

實現 MBean 類通常非常直接明瞭,因爲 MBean 接口要反映現有實體或服務的屬性和管理操作。例如,MBean 中的getLogLevel()  setLogLevel() 方法會直接轉給被 Web 服務器使用的 Logger 上的 getLevel()  setLevel() 方法。JMX 做了一些命名限制。例如,MBean 接口名稱必須以 MBean 結尾,FooMBean 接口的 MBean 類必須叫作 Foo。(可以用更高級的 JMX 特性——動態 MBean 來去除這個限制。)把 MBean 註冊到默認的 MBeanServer 也很容易,如清單 2 所示:


清單 2. 用內置的 JMX 實現註冊 MBean

  public class WebServer implements WebServerMBean { ... }

  ...

  WebServer ws = new WebServer(...);
  MBeanServer server = ManagementFactory.getPlatformMBeanServer();
  server.registerMBean(ws, new ObjectName("myapp:type=webserver,name=Port 8080"));

 

傳遞給 registerMBean()  ObjectName 標識了受管實體。因爲預見到指定應用程序可能包含許多受管實體,所以名稱包含域(清單 2 中的 “myapp”)和許多標識域中的受管資源的鍵-值對。“name” 和 “type” 這兩個鍵是常用的,在使用的時候,name 應當在域中所有的同類 MBean 中能夠唯一地標識受管實體。也可以指定其他鍵-值對,而且 JMX API 還包含進行對象名稱通配匹配的工具。

創建並註冊了 MBean 之後,立即就可以把 jconsole 指向應用程序(在命令行輸入 jconsole)並在 “MBeans” 視圖中查看它的管理屬性和操作。圖 2 顯示了 jconsole 中針對新 MBean 的 Attributes 標籤,圖 3 顯示了 Operations 標籤。使用反射,JMX 可以指出哪個屬性是隻讀的(StartedPort),哪個屬性是可讀寫的(LogLevel),而且 jconsole 允許修改讀寫屬性。如果讀寫屬性的 setter 拋出異常(例如 IllegalArgumentException),JMX 就把異常報告給客戶機。


圖 2. jconsole 中 MBean 的 Attributes 標籤
 

圖 3. jconsole 中 MBean 的 Operations 標籤
 

數據類型

MBean 中的訪問器和操作能夠用任何其簽名形式的原語類型,以及 StringDate 和其他標準庫類。也可以使用這些允許的類型的數組和集合。MBean 方法也可以使用其他可以序列化的數據類型,但是這樣做會造成互操作性問題,因爲類文件也必須對 JMX 客戶機可用。(如果使用 RMI 傳輸,可以使用 RMI 的自動類下載特性完成這項任務。)如果想在管理接口中使用結構化數據類型,還想避免與類可用性相關的互操作性問題,可以使用 JMX 的開放 MBean 特性來表達複合或表格數據。

檢測服務器應用程序

在創建管理接口時,某些參數和操作的特點很自然地就表明這些參數和數據應當被包含在內,例如配置參數、操作統計值、調試操作(例如修改日誌級別或把應用程序狀態導出到文件)、生命週期操作(啓動、停止)。檢測一個應用程序,讓它支持對這些屬性和操作的訪問,通常相當容易。但是,要從 JMX 獲得最大價值,就要在設計時考慮什麼數據在運行時對用戶和操作員有用。

如果用 JMX 瞭解服務器應用程序的工作情況,需要一種標識和跟蹤工作單元的機制。如果使用標準的 Runnable  Callable接口描述任務,通過讓任務類自描述(例如實現toString() 方法),可以在任務生命週期內跟蹤它們,並提供 MBean 方法來返回等候中、處理中和完成的任務列表。

清單 3 中的 TrackingThreadPool 演示的是 ThreadPoolExecutor 的一個子類,它及時給出正在處理中的是哪些任務,以及已經完成的任務的時間統計值。它通過覆蓋 beforeExecute()  afterExecute() 掛鉤,並提供能檢索所蒐集數據的 getter,實現這些任務。


清單 3. 蒐集處理中的任務和平均的任務時間統計值的線程池類

public class TrackingThreadPool extends ThreadPoolExecutor {
    private final Map<Runnable, Boolean> inProgress 
        = new ConcurrentHashMap<Runnable,Boolean>();
    private final ThreadLocal<Long> startTime = new ThreadLocal<Long>();
    private long totalTime;
    private int totalTasks;

    public TrackingThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
       TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        inProgress.put(r, Boolean.TRUE);
        startTime.set(new Long(System.currentTimeMillis()));
    }

    protected void afterExecute(Runnable r, Throwable t) {
        long time = System.currentTimeMillis() - startTime.get().longValue();
        synchronized (this) {
            totalTime += time;
            ++totalTasks;
        }
        inProgress.remove(r);
        super.afterExecute(r, t);
    }

    public Set<Runnable> getInProgressTasks() {
        return Collections.unmodifiableSet(inProgress.keySet());
    }

    public synchronized int getTotalTasks() {
        return totalTasks;
    }

    public synchronized double getAverageTaskTime() {
        return (totalTasks == 0) ? 0 : totalTime / totalTasks;
    }
}

 

清單 4 中的 ThreadPoolStatusMBean 顯示了 TrackingThreadPool 的 MBean 接口,它提供了活動任務、活動線程、完成任務、等候任務的計數,還提供了當前等候執行和正在執行的任務的列表。在管理接口中包含等候和執行任務的列表,讓您既可以看到應用程序的工作難度,又可以看到它目前的工作內容。這個特性不僅讓您可以洞察應用程序的行爲,還能洞察它正在操作的數據集的性質。


清單 4. TrackingThreadPool 的 MBean 接口

public interface ThreadPoolStatusMBean {
    public int getActiveThreads();
    public int getActiveTasks();
    public int getTotalTasks();
    public int getQueuedTasks();
    public double getAverageTaskTime();
    public String[] getActiveTaskNames();
    public String[] getQueuedTaskNames();
}

 

如果任務的重量級足夠,那麼甚至可以再進一步,在每個任務提交時都爲它註冊一個 MBean (然後在任務完成時再取消註冊)。然後可以用管理接口查詢每個任務的當前狀態、運行了多長時間,或者請求取消任務。

清單 5 中的 ThreadPoolStatus 實現了 ThreadPoolStatusMBean 接口,它提供了每個訪問器的明顯實現。與 MBean 實現類中的典型情況一樣,每個操作實現起來都很細碎,所以把實現委託給了底層受管對象。在這個示例中,JMX 代碼完全獨立於受管實體的代碼。TrackingThreadPool 對於 JMX 一無所知;通過爲相關的屬性提供管理方法和訪問器,它提供了自己的編程管理接口。 還可以選擇在實現類中直接實現管理功能(讓 TrackingThreadPool 實現 TrackingThreadPoolMBean 接口),或者單獨實現(如清單 4 和 5 所示)。


清單 5. TrackingThreadpool 的 MBean 實現

public class ThreadPoolStatus implements ThreadPoolStatusMBean {
    private final TrackingThreadPool pool;

    public ThreadPoolStatus(TrackingThreadPool pool) {
        this.pool = pool;
    }

    public int getActiveThreads() {
        return pool.getPoolSize();
    }

    public int getActiveTasks() {
        return pool.getActiveCount();
    }

    public int getTotalTasks() {
        return pool.getTotalTasks();
    }

    public int getQueuedTasks() {
        return pool.getQueue().size();
    }

    public double getAverageTaskTime() {
        return pool.getAverageTaskTime();
    }

    public String[] getActiveTaskNames() {
        return toStringArray(pool.getInProgressTasks());
    }

    public String[] getQueuedTaskNames() {
        return toStringArray(pool.getQueue());
    }

    private String[] toStringArray(Collection<Runnable> collection) {
        ArrayList<String> list = new ArrayList<String>();
        for (Runnable r : collection)
            list.add(r.toString());
        return list.toArray(new String[0]);
    }
}

 

爲了演示這些類如何提供對應用程序操作的內容的瞭解,請考慮這樣一個 Web 搜尋應用程序,它把工作分成兩類任務:獲取遠程頁面,對頁面進行索引。每個任務分別用清單 6 所示的 FetchTask  IndexTask 描述。可以創建 ThreadPoolStatusMBean,提供處理這些任務所使用的線程池的管理接口,並把它用 JMX 註冊。


清單 6. Web 搜尋應用程序中使用的 FetchTask 類

public class FetchTask implements Runnable {
    private final String name;

    public FetchTask(String name) {
        this.name = name;
    }

    public String toString() {
        return "FetchTask: " + name;
    }

    public void run() {  /* Fetch remote resource */  }
}

 

當此程序處理每個頁面時,可能還會對新任務進行排隊以獲取這個頁面上鍊接的頁面,所以在指定時間內,可能會既有獲取任務又有尚未完成的索引任務。能夠正確地判斷正在處理哪個頁面,或者正在等候處理哪個頁面,不僅讓您可以理解應用程序的性能特徵,還可以理解應用程序所操作的數據的特徵。

圖 4 顯示了正在處理 whitehouse.gov 站點的 Web 搜尋程序的快照。從圖中可以看到已經獲取並索引了主頁,程序現在的工作是獲取和索引直接從該主頁鏈接出的頁面。單擊 Refresh 按鈕,可以對應用程序的工作流程進行取樣,它可以提供許多關於應用程序工作情況的信息,卻不需引入大量日誌或者在調試器中運行應用程序。


圖 4. Web 搜尋應用程序中的活動任務和排隊任務
 

結束語

結合平臺內的 JMX 支持和 jconsole JMX 客戶機可以提供一種嚮應用程序添加管理和監視功能的輕鬆方式。即使是沒有具體管理需求的應用程序,爲它們構建這些功能也會讓您對程序的運行及其所處理的數據的性質獲得深入瞭解,而且不需太多的工作和努力。如果應用程序導出管理接口,此接口讓您可以查看它操作的內容,那麼您就會更加了解它的運行狀態——對它是否按預期的方式工作也會更有信心——而不必求助於額外的工具(例如添加日誌代碼或使用調試器或分析器)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章