JConsole をカスタマイズしよう
はじめに
Java SE 6 では、JConsole がいろいろと機能拡張されています。それについては、こちらで紹介したとおりです。
ほとんどの機能拡張はユーザビリティに関連する機能です。On Demand Attach もユーザが見たいときに見られるようにするものですし、GUI の変更はダイレクトにユーザビリティに関わる部分です。
ところが、jconsole にもう 1 つ重要な機能が追加されています。それが jconsole をカスタマイズするための API、Jconsole API です。
この JConsole API を使えば、jconsole のカスタマイズは思いのまま、自作のアプリケーション用のビューを追加するというようなことが実現できてしまいます。
とはいうものの、既存のビューは変更できないので、ビューを追加するといった使いかたになります。
JConsole API
JCconsole を拡張するには、プラグインを追加するという方法をとります。このプラグインを作成するために使う API が JConsole API です。
JConsole API はそれほど使い方が難しいわけではありません。内訳は、クラスが 1 つ、インタフェース 1 つ、enum が 1 つという、こぢんまりした API です。
JConsole API 以外に JMX と Swing の知識が必要となります。コアライブラリで提供されている MXBean だけを扱うのであれば、たいして難しくはないですが、一般の MBean を扱うのであれば JMX Remote が分かっている必要があります。
といっても、たかがしれてるので、恐れる必要はありません。
JConsole API は上述したようにクラス 1 つ、インタフェース 1 つです。
名前 | 説明 |
---|---|
class JConsolePlugin | プラグインのベースとなるクラス。 プラグインを作成するときはこのクラスを派生させる。 |
interface JConsoleContext | JConsole 本体と仲介を行なうインタフェース |
プラグインを作成するには JConsolePlugin クラスを派生させ、次の 2 つのメソッドをオーバライドします。
- Map<String, JPanel> getTabs()
- SwingWorker<?, ?> newSwingWorker()
getTabs メソッドは JConsole に追加するタブを保持させた Map オブジェクトを返します。追加するタブは複数でも構わないので、コレクションが使われます。
Map オブジェクトのキーはタブに表示される文字列です。
もう 1 つの newSwingWorker メソッドは SwingWorker オブジェクトを返すためのメソッドです。SwingWorker クラスは Swing で非同期処理を行なうために使われるクラスです。
SwingWorker については、項をあらためて解説する予定です。まだ、書いてませんが ^^;;
基本的にはこれだけです。
ただ、できれば、ターゲットとなるアプリケーションとの通信が切れたときの処理をいれておいた方がいいです。通信が切れたときには PropertyChangeEvent が発生するので、このイベント処理を記述しておきます。
では、さっそく作ってみましょう。
ヒープの使用量を表示する
まずはヒープの使用量を表示するプラグインを作ってみましょう。実際にはヒープの使用量は[メモリ]タブで表示されているのですが、まぁいいでしょう。
サンプルのソースコード | HeapUsagePlugin.java HeapUsageIndicator.java com.sun.tools.jconsole.JConsolePlugin |
---|
HepUsagePlugin クラスが JConsolePlugin クラスの派生クラス、HeapUsageIndicator クラスが JPanel の派生クラスとなっています。最後のファイルはデプロイメント用に使うファイルです。詳しくは後述します。
まずは HeapUsagePlugin クラスから見ていきます。コンストラクタは後回しにして、getTabs メソッドからです。
@Override public synchronized Map<String, JPanel> getTabs() { if (tabs == null) { indicator = new HeapUsageIndicator(); indicator.setMBeanServerConnection( getContext().getMBeanServerConnection()); tabs = new LinkedHashMap<String, JPanel>(); tabs.put("Heap Usage", indicator); } return tabs; }
tabs はフィールドで定義されている Map<String, JPanel> オブジェクトです。一番はじめに getTabs メソッドがコールされたときに、生成します。
ここでは、タブは 1 つしか作らないので、tabs に保持させるのも 1 つの要素だけです。
ここで LinkedHashMap クラスを使用しているのは、タブの順序を常に同じにさせるためです。HashMap クラスなどを使用すると、要素の順序は保証されません。そこで、要素の追加順序を保持しておける LinkedHashMap クラスを使用したのです。
とはいうものの、要素が 1 つなので、LinkedHashMap クラスを使う意味はないんですけどね ^^;;
tabs に追加するのは、もちろん HeapUsageIndicator オブジェクトです。
次に newSwingWorker メソッドです。
@Override public SwingWorker<?,?> newSwingWorker() { return indicator.newSwingWorker(); }
このメソッドは HeapUsageIndicator クラスに委譲しているだけです。GUI の処理を行なっているのが HeapUsageIndicator クラスなので、妥当でしょう。
最後にコンストラクタです。コンストラクタではターゲットとなる Java VM の接続に関するイベント処理を記述しました。
public HeapUsagePlugin() { addContextPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { // 変更されたプロパティを取得 String prop = ev.getPropertyName(); if (prop == JConsoleContext.CONNECTION_STATE_PROPERTY) { // JavaVMと接続されたらMBeanServerConnectionを取得し // MemoryUsageIndicatorにセットする ConnectionState state = (ConnectionState)ev.getNewValue(); if (state == ConnectionState.CONNECTED && indicator != null) { MBeanServerConnection connection = getContext().getMBeanServerConnection(); indicator.setMBeanServerConnection(connection); } } } }); }
一般的な PropertyChangeEvent の扱いと何ら変ることはありません。イベントの種類は enum の JConsoleContext.ConnectionState に定義されています。
接続したら (CONNECTED)、HeapUsageIndicator オブジェクトに MBeanServer とのコネクションを設定しています。
さて、次は HeapUsageIndicator クラスです。
ここでは単純化のために、ペインの中央にヒープの使用量を出力するだけにしましょう。その部分はコンストラクタで記述しています。
public class HeapUsageIndicator extends JPanel { private JLabel usedValue; private MemoryMXBean memorymxbean; public HeapUsageIndicator() { JPanel panel = new JPanel(); JLabel label = new JLabel("Heap Used (byte)"); panel.add(label); usedValue = new JLabel("0"); panel.add(usedValue); add(panel); }
ラベルが 2 つあり、一方が説明、もう一方がヒープの使用量を出力します。とりあえず、初期値として 0 をいれておきました。
最後に自分自身に貼ります。
次に MBeanServer とのコネクションを設定する処理です。
public void setMBeanServerConnection(MBeanServerConnection connection) { try { memorymxbean = ManagementFactory.newPlatformMXBeanProxy( connection, ManagementFactory.MEMORY_MXBEAN_NAME, MemoryMXBean.class); } catch (IOException e) { e.printStackTrace(); } }
MBeanServerConnection クラスはリモートにある MBeanServer のプロキシになるクラスです。MBeanServerConnection クラスには queryMBean メソッドや getAttribute メソッドなどが定義されています。通常はこれらのクラスを使用して、MBean の属性を取得・設定や操作を行なうのですが、もっと簡単にできる方法があります。
それが MBean のプロキシを作成する方法です。MXBean の場合は ManagementFactory クラスに MXBean のプロキシを作成する newPlatformMXBeanProxy メソッドがあるので、それを使用します。
newPlatformMXBeanProxy メソッドの第 1 引数は MBeanServerConnection オブジェクト、第 2 引数が MXBean の名前 (ObjectName)、第 3 引数が MXBean のクラスになります。
プロキシを作ってしまえば、後はあたかも普通のオブジェクトと同じように使用することができます。
後は、値の更新を行なう部分だけです。この処理は SwingWorker クラスを派生させたクラスで行ないます。
class Worker extends SwingWorker<MemoryUsage,Object> { @Override public MemoryUsage doInBackground() { return memorymxbean.getHeapMemoryUsage(); } @Override protected void done() { try { // doInBackgroundで取得したMemoryUsageを取得 MemoryUsage usage = get(); usedValue.setText(String.format("%d", usage.getUsed())); } catch (InterruptedException ex) { ex.printStackTrace(); } catch (ExecutionException ex) { ex.printStackTrace(); } } }
Swing はスレッドセーフではなく、いくつかのメソッドを除外すれば、Swing が使用しているスレッド以外のスレッドから Swing のコンポーネントに直接アクセスすることは禁じられています。今までは SwingUtilities.invokeLater メソッドなどを使用していましたが、それを SwingWorker クラスで行なうことができます。
SwingWorker クラスを派生させて、2 つのメソッドをオーバーライドします。doInBackground メソッドは Swing が使用しているスレッド以外のスレッドで実行されます。そして、Swing が使用しているスレッドで実行されるのが done メソッドです。
それが分かっていれば、SwingWorker クラスを使いこなすことはそれほど難しくはありません。
まず、バックグラウンドで行なわれる処理ですが、これは MemoryMXBean からヒープの使用状況を表す MemoryUsage オブジェクトを取得しています。
doInBackground メソッドの戻り値は get メソッドで取得することができます。done メソッドの中では、get メソッドで MemoryUsage オブジェクトを取得し、そこから使用量を取りだしてラベルにセットしています。
これで、ソースはできました。次章でコンパイル、実行してみましょう。
コンパイルと実行
当然といえば当然ですが、JConsole API はコアライブラリには含まれていません。そのため、コンパイルするにはクラスパスの設定が必須です。
ここでは、JDK がインストールされたディレクトリを JAVA_HOME という環境変数に保持させてあったとします。
その場合、Windows では次のようにコンパイルします。
C:\temp>javac -cp "%JAVA_HOME%\lib\jconsole.jar";. HeapUsagePlugin.java
コンパイルができたら、JAR ファイルにまとめなくてはなりません。このときに、おまじないをしましょう。
META-INF ディレクトリを作成し、そのしたに services ディレクトリを作成します。そして、services ディレクトリには com.sun.management.JConsolePlugin というファイルを配置します。
com.sun.management.JConsolePlugin ファイルの中身は、たった 1 行
HeapUsagePlugin
つまり、このファイルは JConsole に対して、どのクラスをロードすればいいかを示しています。プラグインの型としては com.sun.management.JConsolePlugin クラスで、そのコンクリートクラスが HeapUsagePlugin クラスというわけです。
JAR ファイルを作成するときには、この com.sun.management.JConsolePlugin ファイルも一緒に含めます。
C:\temp>jar cvf heapusage.jar *.class META-INF\services\com.sun.management.JConsolePlugin
これでパッケージングができました。後は実行です。
実行するときには -pluginpath で JAR ファイルを指定します。
C:\temp>jconsole -pluginpath heapusage.jar
すると、下図のように [Heap Usage] というタブが表示されるはずです。
このタブを表示させてみましょう。
ちょっとヘボいですが、ちゃんと表示されました。
ところで、この表示をずっと見つめていてください。ソースには定期的に値を更新する処理はまったく書いていないのですが、値が更新されているのがお分かりのはずです。
この更新処理は JConsole が勝手にやってくれます。そのために SwingWorker クラスを使用しているのです。更新のタイミングは他のグラフなどと同時で、デフォルトでは 4 秒です。
この間隔は JConsole の -interval オプションで変更できます。
おまけ
ここで示したサンプルはとても単純ですが、後は GUI の問題だけなので、いろいろとカスタマイズしてみてください。
たとえば、こんなのはいかがですか?
これはフリーのグラフ描画ライブラリである JFreeChart を使用したものです。
このぐらいであれば、本当に簡単にグラフを描けてしまいますよ。ただ実行するときにちょっとだけ注意点があります。
JFreeChart を使用すると、どうしても JAR ファイルが複数になってしまいます。そのときには -pluginpath に複数の JAR ファイルを記述します。順番は問いません。
はじめ、-J-Djava.class.path で設定するのかと思って、いろいろ試してみたのですが、全然ダメ。最後にダメもとで -pluginpath に並べたら動作したのでした。
サンプルのソースコード | HeapChartPlugin.java HeapChartIndicator.java com.sun.tools.jconsole.JConsolePlugin |
---|
JConsole API を使えば、このように JConsole のビューを自在にカスタマイズできます。つまり、特定のアプリケーションに最適な管理画面を作り込むことができるわけですね。
ほとんどのアプリケーションサーバは HTTP を使用して、管理画面を作っていましたが、JConsole でリッチな管理画面を作ることも可能になのです。 これは便利ですよ。
(Feb. 2007)