じゃじゃ馬ならし

 

より便利になった JConsole

はじめに

JConsole は J2SE 5.0 から取り入れられた JMX に対応したソフトウェア管理のためのツールです。特に JSR-174 で策定された JVM の状態を表す MXBean を参照することができるので、とても便利なツールなのです。

しかも、Java SE 6 ではもっと便利になったのです。それをさっそくご紹介しましょう。

 

On Demand Attach

J2SE 5.0 では JConsole で JVM の状態を参照するには、ターゲットとなる java の起動オプションが必要でした。ローカルの場合は次のようにアプリケーションを起動させます。

C:\temp>java -Dcom.sun.management.jmxremote [ApplicationClass]

これはこれでいいのですが、問題はこのオプションをつけないと JConsole で見ても何も情報が得られないということです。

Mustang ではこの点が改良されて、この起動オプションをつけなくても JConsole で参照することができるようになりました。たとえば、OutOfMemoryError の解説で使用したサンプル OutOfMemorySample で確かめてみましょう。

まず、起動オプションをつけてみます。その状態で JConsole を起動すると、以下のようになります。

jconsole

OutOfMemorySample を選択して、[接続] すると JVM の情報を参照することができます。

次に起動オプションをつけないで実行してみましょう。

jconsole

起動オプションをつけた場合とまったく一緒です。OutOfMemorySample を選択すれば、[接続] できてしまいます。

参照できる情報は何も変わりません。

だいたい、Java VM の状態を見たいと思うのは、アプリケーションの動きがなんか怪しくなってからですが、そういうときに限って起動オプションを付け忘れているものです。

でも、Java SE 6 からはいつでも JConsole で状態を見ることができるようになったわけです。ただし、同じマシンでアプリケーションを動作しているのと同じユーザという条件つきですが。

このように要望があったときにいつでも Java VM にアタッチできることを On Demand Attach と呼んでいます。

もう 1 つうれしい点があります。それは Applet や Java Web Start で起動したアプリケーションでも JConsole が接続できるという点です。

 

GUI の強化

JConsole の機能拡張は On Demand Attach に限りません。使い勝手もかなり向上しました。

とりあえず、JConsole を起動させてみてください。今までとは違うことにすぐ気がつきますから。

概要

まずは J2SE 5.0 に付属している JConsole を起動させてみます。

jconsole

字ばっかりで、見る気がおきません ^^;; メモリやスレッドといったタブを選ばないと見るべき情報は少ないですね。

次に Java SE 6 に付属している JConsole を実行してみます。

jconsole

こちらはグラフが表示されて見やすくなっています。実際、このデフォルト表示だけでも、それなりに役に立ってしまいます。

グラフはヒープメモリ、スレッド数、クラスロード数、CPU 使用率の 4 つです。ヒープ、スレッド、クラスロードはそれぞれ対応する MXBean がありますが、CPU 使用率はどこから取得しているのでしょう。

こういうときのために (?) ソースが公開されているのですから、見てみましょう。ちなみに、JDK に付属している src.zip に入っていないので、Java SE 6 のダウンロードページにある Java SE 6 JDK Source Code からソースをダウンロードしましょう。

といっても、実際には java.net の JDK6 プロジェクトのダウンロードページに飛ばされるだけです。

さて、JConsole のソースです。JConsole は sun.tools.JConsole パッケージにあります。概要タブを生成しているのは SummaryTab クラスです。

このクラスで CPU 使用率は SummaryTab クラスの内部クラスである CPUOverviewPanel クラスの updateCPUInfo メソッドで更新されています。

    public void updateCPUInfo(Result result) {
        if (prevUpTime > 0L && result.upTime > prevUpTime) {
            // elapsedCpu is in ns and elapsedTime is in ms.
            long elapsedCpu = result.processCpuTime - prevProcessCpuTime;
            long elapsedTime = result.upTime - prevUpTime;
 
            // cpuUsage could go higher than 100% because elapsedTime
            // and elapsedCpu are not fetched simultaneously. Limit to
            // 99% to avoid Plotter showing a scale from 0% to 200%.
            float cpuUsage =
                     Math.min(99F,
                     elapsedCpu / (elapsedTime * 10000F * result.nCPUs));
 
            getPlotter().addValues(result.timeStamp, Math.round(cpuUsage));
            getInfoLabel().setText(getText(cpuUsageFormat,
                                           String.format("%.2f", cpuUsage)));
        }
        this.prevUpTime = result.upTime;
        this.prevProcessCpuTime = result.processCpuTime;
    }

CPU の使用率は赤字の部分で計算されているのでした。ここで出てきている elapsedCpu などの変数はその上の部分で定義されています。

ようするにプロセスの CPU 時間を UP 時間で割ったものですね。

これを見ると、Result クラスのフィールド processCpuTime などが問題になってきます。Result クラスは単にデータを保持しているだけのクラスなので、実際にはそのフィールドに値を代入している部分が重要です。

これは formatSummary メソッドで行なわれています。

    synchronized Result formatSummary() {
        Result result = new Result();
        ProxyClient proxyClient = vmPanel.getProxyClient();
        if (proxyClient.isDead()) {
            return null;
        }
 
        buf = new StringBuilder();
        append("<table cellpadding=1>");
 
        try {
            RuntimeMXBean         rmBean     = proxyClient.getRuntimeMXBean();
            CompilationMXBean     cmpMBean   = proxyClient.getCompilationMXBean();
            ThreadMXBean          tmBean     = proxyClient.getThreadMXBean();
            MemoryMXBean          memoryBean = proxyClient.getMemoryMXBean();
            ClassLoadingMXBean    clMBean    = proxyClient.getClassLoadingMXBean();
            OperatingSystemMXBean osMBean    = proxyClient.getOperatingSystemMXBean();
            com.sun.management.OperatingSystemMXBean sunOSMBean  =
                                               proxyClient.getSunOperatingSystemMXBean();
 
                <<途中、省略>>
  
            append(newRightTable);
            result.upTime = rmBean.getUptime();
            append("Uptime", formatTime(result.upTime));
            if (sunOSMBean != null) {
                result.processCpuTime = sunOSMBean.getProcessCpuTime();
                append("Process CPU time", formatNanoTime(result.processCpuTime));
            }
			
                <<以下、省略>>
			

関係のない部分も多いので見にくいのですが、赤字の部分が関連する部分です。

ようするに UP 時間は RuntimeMXBean から、プロセスの CPU 時間は OperatingSystemMXBean から取得しているようです。

ここで使われている com.sun.management.OperatingSystemMXBean は通常の OperatingSystemMXBean を派生させたものです。具体的にはプロセス CPU 時間、Java VM がコミットしているメモリのサイズ、スワップ量、 物理メモリ量などが取得できるようになっています。

と、これで謎はとけました。それでは、本題に戻って JConsole の改良点を再び見ていきましょう。

スレッド

メモリタブで表示される内容は J2SE 5.0 も Java SE 6 もほとんど変りません。 クラスタブも一緒です。

スレッドタブも一緒... あっ、ちょっとだけ違いました。違いは一番下の方。

jconsole

[デッドロックを検出する]ボタンが付いたのでした。

実際にデッドロックを発生させてどうなるか試してみましょう。

サンプルのソースコード DeadlockSample.java

このサンプルでは 2 つのスレッド "Sample Thread 1" と "Sample Thread 2" がデッドロックを起こすはずです。

まずは単にスレッドタブで、この 2 つのスレッドがあるかどうか確かめてみましょう。

jconsole

ちゃんとありますね。ここで[デッドロックを検出する]ボタンを押してみます。するとスレッド一覧のところにもう 1 つタブがあらわれて、そこにデッドロックしているスレッドが表示されます。

jconsole

ちゃんと、Sample Thread 1 と Sample Thread 2 が検出されました。ここで、表示されているスレッドを選択するとスタックトレースも表示されます。

jconsole

これはかなり便利ですね。

これ以外の部分でも、MBean タグで属性と操作が左側のツリーに表示されるようになったり、ノティフィケーションも表示が変ったりしています。

こういう変化は実際に使ってみないとよく分かりません。ぜひ、一度使ってみてください。

JConsole はここで紹介した部分以外にもう 1 つ機能拡張がなされています。それはまた項をあらためて紹介することにしましょう。

 

(Nov. 2005)

(改訂 Feb. 2007) JConsole の解説を独立させ、GUI の改良について追加