咖啡日语论坛

 找回密码
 注~册
搜索
查看: 15683|回复: 30

Java SE 6完全攻略

[复制链接]
发表于 2007-5-25 13:57:44 | 显示全部楼层 |阅读模式
回复

使用道具 举报

 楼主| 发表于 2007-5-25 14:14:52 | 显示全部楼层
Java SE 6 Hard Days Night


待ちに待っていた,Java SE 6がもうすぐリリースされます。されるはずです。されると思うのですが...
なぜ,こんなに弱気なのかというと,もともとJava SE 6は今年5月のJavaOneの前にリリースされるされる予定でした。それが,10月に延び,さらに12月に延びたという経緯があるのです。
とはいうものの,最近のビルドでは新しい機能の追加はほとんどなく,バグフィックスがほとんどです。リリースが近いことは確実なようです。
そこで,今月からJava SE 6について取り上げていこうと思います。
Java SE 6では,スクリプトのサポートやWebサービス系のAPIが導入されたことなどが話題になりますが,それ以外にもたくさんの機能が取り入れられています。機能としてみればたいしたことがなくても,役に立つ機能が多くあるのです。
少しでも早くJava SE 6を試してみたいと思われる方も多いでしょう。Java SE 6の特徴として,java.netのJDK6プロジェクトでほぼ毎週ビルドが公開されていることがあります。バイナリだけでなく,ソースもほぼすべて公開されています。最新のビルドをいつでも試せるのですから,これを逃す手はありませんよ。
なお,今月はJDK 6 build 99を使用していきます。12月に正式にリリースされるJava SE 6とは動作が異なる可能性がありますが,ご了承ください。
OutOfMemoryErrorのハンドリングまず,はじめに取りあげるのは,OutOfMemoryErrorのハンドリングについてです。
意外ではないですか?
Java SE 6といえば,スクリプトのサポートやWebサービスが取り上げられることが多いはずです。ところが,OutOfMemoryErrorなんて。
もちろん,スクリプトやWebサービスは重要な機能です。しかし,筆者はOutOfMemoryErrorのハンドリングなどを含んだ,Javaの保守,管理に関する機能が,より重要であると考えています。Java SE 6で導入された新しい機能を全く使わなくても,Java SE6に移行するだけでデバッグや保守が格段にしやすくなるのです。
さて,本題のOutOfMemoryErrorのハンドリングです。
とりあえず,説明は抜きにして,以下のプログラムを実行してみてください。なお,import文は省略してあります。
      public class OutOfMemorySample {
    private static final String FORMULA = "Hello, World! ";
    private List messages = new ArrayList();

    public OutOfMemorySample() {
        while(true) {
            appendMessage();
        }
    }

    private void appendMessage() {
        messages.add(FORMULA);
    }

    public static void main(String[] args) {
        new OutOfMemorySample();
    }
}  
ごくごく簡単なプログラムなので,解説する必要はないですね。単にリストに文字列を追加していくだけのプログラムです。
このプログラムをJ2SE 5.0で実行してみると...
  [size=0.9em]    C:\temp>java OutOfMemorySample
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space  
当然のごとく,OutOfMemoryErrorが発生しました。OutOfMemoryErrorの発生は想定どおりなのですが,問題はどこでOutOfMemoryErrorが発生したかということです。
このぐらいの小さなプログラムだと,OutOfMemoryErrorの発生個所はすぐわかります。しかし,大規模なシステムになれば,OutOfMemoryErrorの発生源を探すのは並大抵のことではありません。にもかかわらず,OutOfMemoryErrorが発生したときにスタック・トレースが表示されないのは大問題です。
それでは,このプログラムをJava SE 6で動かしてみましょう。
  [size=0.9em]    C:\temp>java OutOfMemorySample
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2760)
        at java.util.Arrays.copyOf(Arrays.java:2734)
        at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
        at java.util.ArrayList.add(ArrayList.java:351)
        at OutOfMemorySample.appendMessage(OutOfMemorySample.java:17)
        at OutOfMemorySample.(OutOfMemorySample.java:12)
        at OutOfMemorySample.main(OutOfMemorySample.java:21)  
こんどはちゃんとスタック・トレースが表示されました。このスタック・トレースから,リストの容量を増やして要素のコピーを行っている部分でOutOfMemoryErrorが発生していることがわかります。
これならば,どこに問題があるか一目瞭然です。
OutOfMemoryErrorでスタック・トレースが出ることなど,機能としてみればほんのわずかなことかもしれません。しかし,こんなわずかな機能であっても,デバッグの効率は雲泥の差です。
逆にいえば,なぜこんな簡単なことを今までできなかったのだろう,という感じですね。
なにはともあれ,この機能だけでもJava SE 6に乗りかえる価値はありますよ。

[ 本帖最后由 bgx5810 于 2007-5-25 14:17 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 14:18:53 | 显示全部楼层
情報が見たいときにすぐ見える


今週も先週と同様に,ソフトウエアの保守の機能を紹介します。何らかのシステムを稼働させているときに,調子がおかしくなったことはありませんか。動作が重くなったり,Webアプリケーションだとレスポンスが遅くなったりとか。そんなときに役に立つのがJ2SE 5.0で導入されたMXBeanです(MXBeanの詳細は本連載の「JMXでソフトの健康をがっちり管理」をご覧ください)。
MXBeanが提供している情報に簡単にアクセスするためのツールがjconsoleです。
J2SE 5.0では,jconsoleでJava VMにアタッチするには,システムの起動時にオプション-Dcom.sun.management.jmxremoteを指定しておかなければなりません。これがくせ者でした。
普通はそんなオプションをつけて起動しませんよね。調子が悪くなってから,はじめて「あぁ,起動オプションをつけておくのだった」と後悔するのが関の山です。
ところが,Java SE 6では違うのです。起動オプションを指定しなくても,いつでもJava VMにアタッチできるようになりました。
ただし,アタッチできるのは「Java VMをローカルで起動させており,起動したのと同一アカウントの場合」に限られます。第三者がリモートから勝手にアタッチしてしまうのは困るので,この制限は妥当ですね。
例えば,次のプログラムを使って試してみましょう。先週使用したプログラムにウェイトを付けただけの簡単なプログラムです。
      public class OutOfMemorySample2 {
    private static final String FORMULA = "Hello, World! ";
    private List messages = new ArrayList();
  
    public OutOfMemorySample2() {
        while(true) {
            appendMessage();

            try {
                Thread.sleep(1L);
            } catch (InterruptedException ex) {
                break;
            }
        }
    }

    private void appendMessage() {
        messages.add(FORMULA);
    }
  
    public static void main(String[] args) {
        new OutOfMemorySample2();
    }
}  
このプログラムをまずJ2SE 5.0で実行し,次にjconsoleを実行してみます。jconsoleはJ2SE 5.0とJava SE 6のどちらのものでもかまいません。
まず,-Dcom.sun.management.jmxremoteを指定して起動してみましょう。
      C:\temp>java -Dcom.sun.management.jmxremote OutOfMemorySample2  
jconsoleを起動すると,アタッチ可能なJava VMのリストが表示されます(図1)。ちゃんと,OutOfMemorySample2が表示されていることがわかります(なお,図1はJ2SE 5.0のjconsoleのスクリーンショットです)。
  
    [tr]        図1 J2SE 5.0でプログラムを実行(オプションあり)      [/tr]
  

次に,J2SE 5.0で起動オプションを指定しないで,OutOfMemorySample2を起動してみます。この場合,図2に示すように,リストには表示されません。
  
    [tr]        図2 J2SE 5.0でプログラムを実行(オプションなし)      [/tr]
  

それでは,Java SE 6で起動してみましょう。もちろん,起動オプションは指定しません。起動オプションがなくても,図3に示すように,ちゃんとリストに表示されました。
  
    [tr]        図3 Java SE 6でプログラムを実行(オプションなし)      [/tr]
  

jconsoleはJava SE 6のものを使用していますが,jconsoleもリストに含まれています。これもオプションなしでJava VMにアタッチできるためなのです。
この機能はオンデマンドアタッチと呼ばれています。名前はともかく,見たいときにすぐに情報が見えるのは,ソフトウエアを保守するうえでとても強力な武器になるはずです。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 14:19:44 | 显示全部楼层
jconsoleの機能向上


今週も先週に引き続きソフトウエアの保守に関する話題です。今週の主役はjconsoleです。
jconsoleはもともと簡易的な管理ツールとしてJ2SE5.0から導入されたものです。簡易管理ツールとしての役割は同じですが,Java SE 6では,使いやすさが格段に向上しました。Java SE6での変更点を,J2SE 5.0と比較していきましょう。
オンデマンドアタッチについては先週紹介しました。この機能はjconsoleの機能ではなく,Java VMの機能です。しかし,この機能によってjconsoleが使いやすくなったのは事実です。
jconsoleを起動すると,J2SE 5.0でもJava SE 6でも[概要]が表示されます。図1がJ2SE 5.0,図2がJavaSE 6のスクリーンショットです。見れば一目瞭然,J2SE 5.0は文字で表示されていたものが,Java SE 6ではグラフ表示されています。
文字ではその時点での値しかわからないのに比べ,グラフにすることでトレンドを見ることができます。はっきりいえば,J2SE 5.0の[概要]は役立たずだったわけです。
Java SE 6 では[概要]だけでも十分な情報を得ることができるので,筆者はjconsoleを使う場合,[概要]のままにしておくことが多くなりました。
  
    [tr]        図1 J2SE 5.0の[概要]        図2 Java SE 6の[概要]       [/tr]
  

上部のタブの順番も若干変更されています。[メモリ]などのタブで表示される情報はほとんど違いありません。しかし,[VMの概要]タブは表示される情報が変化しています。Java SE 6では,J2SE5.0の[概要]タブと[VM]タブの情報を,[VMの概要]タブで一度に見られるようになっています。
  
    [tr]        図3 J2SE 5.0の[VM]        図4 Java SE 6の[VMの概要]       [/tr]
  

機能的に向上したのは,スレッドの間のデッドロックを検出できるようになったことです。[スレッド]タブの下部に[デッドロックを検出する]ボタンがあります。このボタンを押すと,デッドロックに陥ったスレッドを表示します。
  
    [tr]        図5 Java SE 6の[デッドロックを検出する]ボタン       [/tr]
  

[MBean]タグも表示方法がずいぶん変更されました。J2SE 5.0ではタブが2重になっていたのが,Java SE6では左側のツリー表示で[属性],[操作],[通知]が表示されるようになっています。どちらが好みかは人によりますが,筆者はJava SE6のほうがすっきりしていると感じています。
  
    [tr]        図6 J2SE 5.0の[VM]        図7 Java SE 6の[VMの概要]       [/tr]
  

ところで,図2の概要にはMXBeanから直接得られない情報が含まれています。右下のグラフで表示されているCPU使用状況です(図8)。
  
    [tr]        図8 CPU使用状況       [/tr]
  

MXBeanが拡張されたのかと思ってJavadocを調べてみても,CPU使用状況などはどこにもありません。
そこで,jconsoleのソースコードを調べてみました。Java SE6はビルドが毎週公開されていますが,バイナリだけでなくソースコードもすべて公開されています。もちろん,jconsoleのソースコードもちゃんと含まれています。こういうときは,ソースコードがあるととても役に立ちますね。
問題の個所はsun.tools. jconsole.SummaryTabクラスにありました。
      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;
}  
updateCPUInfoメソッドのResultオブジェクトはMXBeanとのデータの受け渡しに使うクラスのようです。このメソッドを見ると,CPU使用量はOperatingSystemMXBeanから取得できるプロセスタイムと,RuntimeMXBeanから取得できるアップタイムから求めているに過ぎないようです。
概算値でしかありませんが,有用な情報であるのは確かですよ。
もしjconsoleを使ったことがないのでしたら,この機会にぜひ使ってみてください。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 14:20:26 | 显示全部楼层
Windowsでも利用できるようになった管理ツール


J2SE 5.0からJava SEでもソフトウエアの管理機能が導入されました。JMXとMXBeanが目玉であることには変わらないのですが,そのほかにも次に示すツール群が提供されています。
  
  • jps
  • jstat
  • jinfo
  • imap
  • jstack
ところが,J2SE 5.0ではjpsとjstat以外の三つのツールはWindowsでは提供されませんでした。それが,Java SE6になって,ようやくWindowsでも使えるようになったのです。Windows以外のプラットフォームをお使いの方には旧聞に属すると思いますが,簡単に説明していきましょう。
jinfojinfoはJava VMの構成情報を参照,もしくは設定するためのツールです。
HotSpot VMには-XX:ではじまる起動オプションがあることはご存じでしょうか。例えば,GCの情報を出力するための-XX:+PrintGCなどがあります。詳しくはHotSpot VMオプションのWebページを参照してください。
jinfoを使うと,これらの起動オプションの値を参照したり,あとから設定することができます。
例えば,先ほどの-XX:+PrintGCを例にとってみましょう。jinfoなどのツールはすべてプロセスIDを指定する必要があります。プロセスIDはjpsツールで調べることができます。
      C:\temp>jps
2676 OutOfMemorySample2
3904 Jps

C:\temp>jinfo -flag PrintGC 2676
-XX:-PrintGC

C:\temp>jinfo -flag +PrintGC 2676

C:\temp>jinfo -flag PrintGC 2676
-XX:+PrintGC  
はじめのjpsでプロセスIDが2676だということがわかります。単にオプションの値がどうなっているかを調べるには-flagのあとにオプションの名称を指定します。ここでは,出力が-XX:-PrintGCになっています。PrintGCの前に"-"がついているので,GCの情報は出力されていないことがわかります。
それではこのオプションの設定を変更してみましょう。そのためには,オプションの前に+もしくは-を付加します。-flag +PrintGCとすることで,-XX:+PrintGCと設定したことになります。
こうすれば,ターゲットになるアプリケーション(この場合はOutOfMemorySample2)が,-XX:+PrintGCを起動オプションに指定したのと同じ出力をすることを確認できるはずです。
ところで,jinfoは使えるようになったものの,まだWindowsは差別を受けています。jinfoには-syspropsというオプションがあり,システムプロパティの一覧を表示できます。しかし,依然としてWindowsでは使えません。
jmapjmapはヒープ・ダンプを取得するために使用するツールです。取得したヒープ・ダンプはgdbなどで読み込むことができます。
      C:\temp>jps
2316 OutOfMemorySample2
2508 Jps

C:\temp>jmap -dump:file=heap.map 2316
Heap dump file created  
-dump:file=[ファイル名]でヒープ・ダンプのファイル名を指定します。
ヒープ・ダンプ以外に,インスタンスのヒストグラムを出力することもできます。
      C:\temp>jmap -histo 2316
num   #instances    #bytes  class name
--------------------------------------
  1:       318     2694104  [Ljava.lang.Object;
  2:       859      146784  [C
  3:       779      116104  <symbolKlass>
  4:        35       65928  [B
  5:       723       23136  java.util.TreeMap$Entry
  6:       139       22176  <constMethodKlass>
  7:       862       20688  java.lang.String
  8:        25       12832  <constantPoolKlass>
  9:       139       12256  <methodKlass>
10:        32       10240  <objArrayKlassKlass>
11:        25        9072  <instanceKlassKlass>
12:        66        6336  java.lang.Class
13:       175        5624  [Ljava.lang.String;
14:        25        4704  <constantPoolCacheKlass>
     ... (以下,略) ...  
jstackjstackはスレッド・ダンプを出力するためのツールです。スレッド・ダンプとは,稼働しているすべてのスレッドのスタック・トレースのことです。
通常,スレッド・ダンプは,Windowsであればjavaが動作しているコマンドプロンプトからCtrl+Breakキーを押すことで取得できます(Unix系のOSではkill -QUIT [pid])。これをツールで行えるようにしたのがjstackです。
  [size=0.75em]    C:\temp>jps
2316 OutOfMemorySample2
2508 Jps

C:\temp>jstack 2316
2006-09-28 05:01:18
Full thread dump Java HotSpot(TM) Client VM (1.6.0-beta2-b86 mixed mode, sharing
):

"Low Memory Detector" daemon prio=6 tid=0x02b1ec00 nid=0x260 runnable [0x0000000
0..0x00000000]
   java.lang.Thread.State: RUNNABLE

"CompilerThread0" daemon prio=10 tid=0x02b1c800 nid=0xf34 waiting on condition [
0x00000000..0x02d7f91c]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" daemon prio=10 tid=0x02b1b400 nid=0xa14 waiting on condition [
0x00000000..0x02d2fea8]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=10 tid=0x02b1a400 nid=0xa7c runnable [0x00000000
..0x00000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=8 tid=0x02abdc00 nid=0xe4c in Object.wait() [0x02c8f000.
.0x02c8fa94]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x22ecc0a0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116)
        - locked <0x22ecc0a0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)

     ... (以下,略) ...  
jstackを使用すれば,デッドロックなどで動作が止まってしまった場合でも,どこで止まっているかを調べることができます。
このようなツールを有効に活用できれば,ソフトウエアの管理を効率化することが可能です。ぜひ,活用してみてください。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 14:21:24 | 显示全部楼层
プロファイラ hprofとよき相棒 jhat


アプリケーションのパフォーマンス・チューニングなどで使用するツールといえば,まず第一にあげられるのがプロファイラです。最近は,EclipseのTPTPや,NetBeansProfilerなどフリーで使えるプロファイラが増えてきたので,使ってみたことがある方も増えてきていると思います。
意外に知られていないのですが,Java SEにも標準でプロファイラが付属しています。それがhprofです。
Java 2 SE 5.0からは「-agentlib:hprof」という起動オプションでhprofを起動できます(J2SE 1.4.までは「-Xrunhprof」)。使い方はヘルプ・オプションで表示できます。
  [size=0.8em]    C:\temp>java -agentlib:hprof=help

     HPROF: Heap and CPU Profiling Agent (JVMTI Demonstration Code)

hprof usage: java -agentlib:hprof=[help]|[<option>=<value>, ...]

Option Name and Value  Description                    Default
---------------------  -----------                    -------
heap=dump|sites|all    heap profiling                 all
cpu=samples|times|old  CPU usage                      off
monitor=y|n            monitor contention             n
format=a|b             text(txt) or binary output     a
file=<file>            write data to file             java.hprof[{.txt}]
net=<host>:<port>      send data over a socket        off
depth=<size>           stack trace depth              4
interval=<ms>          sample interval in ms          10
cutoff=<value>         output cutoff point            0.0001
lineno=y|n             line number in traces?         y
thread=y|n             thread in traces?              n
doe=y|n                dump on exit?                  y
msa=y|n                Solaris micro state accounting n
force=y|n              force output to <file>         y
verbose=y|n            print messages about dumps     y

Obsolete Options
----------------
gc_okay=y|n

Examples
--------
  - Get sample cpu information every 20 millisec, with a stack depth of 3:
      java -agentlib:hprof=cpu=samples,interval=20,depth=3 classname
  - Get heap usage information based on the allocation sites:
      java -agentlib:hprof=heap=sites classname

Notes
-----
  - The option format=b cannot be used with monitor=y.
  - The option format=b cannot be used with cpu=old|times.
  - Use of the -Xrunhprof interface can still be used, e.g.
       java -Xrunhprof:[help]|[<option>=<value>, ...]
    will behave exactly the same as:
       java -agentlib:hprof=[help]|[<option>=<value>, ...]

Warnings
--------
  - This is demonstration code for the JVMTI interface and use of BCI,
    it is not an official product or formal part of the JDK.
  - The -Xrunhprof interface will be removed in a future release.
  - The option format=b is considered experimental, this format may change
    in a future release.  
それでは,JDKに付属しているサンプルのJava2Demoを題材にプロファイルをしてみましょう。
Java2Demoを実行して,適当なところで終了させてください。すると,「Dumping Java heap ...」というメッセージが表示されて,プロファイル結果が出力されます。
      C:\temp>java -agentlib:hprof -jar Java2Demo.jar
Dumping Java heap ... allocation sites ... done.

C:\temp>  
デフォルトではプロファイル結果はjava.hprof.txtというファイルに出力されます(fileオプションを使えば,任意のファイル名を指定できます)。このファイルをエディタなどで開いてみてください。
java.hprof.txtに出力されるのはデフォルトではヒープの情報だけです。実行時間の出力を行うには「cpu=samples」などのオプションを指定します。
ヒープの情報はトレース(オブジェクトが生成された時のスタック・トレース),ダンプ,サイツ(アロケートされたオブジェクトの個数でソートされた表)の順番で出力されます。
例として,Java2Demoのプロファイル出力を見てみましょう。まずは意味がわかりやすいサイツから。
サイツはプロファイル出力の最下部に記述されています。
  [size=0.9em]    SITES BEGIN (ordered by live bytes) Sun Oct 22 09:40:44 2006
          percent          live          alloc'ed  stack class
rank   self  accum     bytes objs     bytes  objs trace name
    1 16.40% 16.40%   1307872   31   2341888    41 306111 int[]
    2  7.65% 24.06%    610288    1    610288     1 309184 int[]
    3  6.78% 30.84%    540880    4    540880     4 305413 int[]
    4  5.74% 36.58%    458000 6846    458000  6846 300000 char[]
    5  3.89% 40.48%    310528   27    310528    27 305378 byte[]
    6  2.05% 42.53%    163544 6799    163544  6799 300000 java.lang.String

              ~以下略~  
サイツはアロケーションされた個所ごとにオブジェクトの個数をカウントしています。このため,rankの1番目から3番目にint[ ]が並んでいるように,同じオブジェクトでもサイツにリストアップされることがあります。
rankが1のint[ ]は現在使用されているサイズが1307872バイト,個数が31個です。トータルだとサイズが2341888バイト,個数が41個です。
このint[ ]はどこでアロケーションされているのでしょう。これを調べるには,stack traceの下に記述されている数字を使用します。int[ ]の場合は306111です。
アロケーションしている場所を示すのがトレースです。トレースにはすべて番号が付けられています。先ほどのint[ ]の306111というのがその番号に相当します。
  [size=0.7em]    TRACE 306111:
        java.awt.image.DataBufferInt.(DataBufferInt.java:41)
        java.awt.image.Raster.createPackedRaster(Raster.java:458)
java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1015)
        java.awt.image.BufferedImage.(BufferedImage.java:312)  
これを見ると,DataBufferIntオブジェクトの中でint[]がアロケーションされていることがわかります。DataBufferIntクラスはBufferedImageクラスでイメージを保持するために使用されるクラスです。つまりイメージが多く使われていることを示しているわけです。
オブジェクトのサイズなどを調べるのにはダンプを参照します。このときもトレースの番号が識別子になっています。
と,こうやって見ていけばいいのですが,如何せんファイルのサイズが大きすぎて扱いが大変です。ここで使用したJava2Demoでも,1分間程度の実行に対するプロファイル結果は14MBにも達してしまいました。
そこで,もう少しhprofを扱いやすくするために生まれたのが,JDK 6に添付されることになったjhatというツールです
注: JRE 6にはjhatは付属していません。
プロファイル解析ツール jhatjhatはもともとAdvanced Programming for the Java 2 Platformという本の中のサンプルとして作られました。そのときはHAT(Heap Analysis Tool)という名前でした。
その後,HATはjava.netで公開され,最終的にJDK 6に付属することになったのです。そして,他のツール同様に「j」が頭に付き,jhatという名前になりました。
それでは,さっそくjhatを使ってみましょう。
jhatを使うには,バイナリ形式のhprofの出力ファイルを使用します。デフォルトではテキスト形式になってしまうので,「format=b」を指定します。
      C:\temp>java -agentlib:hprof=format=b -jar Java2Demo.jar
Dumping Java heap ... allocation sites ... done.

C:\temp>  
「hprof=format=b」と記述するのはなんか変な感じですが,こういうものだと思ってください。デフォルトではhprofの出力ファイルはjava.hprofになります。jhatは,hrpofの出力ファイル名を引数にして起動します。
      C:\temp>jhat java.hprof
Reading from java.hprof...
Dump file created Sun Oct 22 16:36:00 JST 2006
Snapshot read, resolving...
Resolving 108592 objects...
Chasing references, expect 21 dots.....................
Eliminating duplicate references.....................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.  
jhatはWebサーバーとして動作するので,Webブラウザで解析結果を見ることができます。デフォルトのポート番号は7000なので,「http://localhost:7000/」で参照します。ポート番号は-portオプションで変更できます。
  
    [tr]        図1 jhatの結果の閲覧      [/tr]
  

「http://localhost:7000/」で閲覧できるのはクラスの一覧です。ただし,プラットフォームのクラスは含まれず,アプリケーションに含まれるクラスだけです。
クラスのリンクをたどると,クラスに関する情報を閲覧できます。この情報には参照関係も含まれており,閲覧しているクラスに至る参照,このクラスが参照しているクラスなどを確認できます。
トップページの最下部には,これら以外の情報に対するリンクがあります。例えば,「Show heaphistogram」ではjava.hprof.txtのサイツのようなヒープ・ヒストグラムを見ることができます(図2)。ただし,サイツではアロケーションごとに分かれていたものが,ヒープ・ヒストグラムではまとめられています。
テーブルのカラム名をクリックすることで,ソートする項目を変更できます。デフォルトではトータル・サイズでソートされています。
  
    [tr]        図2 ヒープのヒストグラム      [/tr]
  

同様にトップページ最下部にある「Show instance counts for all classes」から,インスタンスの一覧を見ることができます(図3)。
  
    [tr]        図3 インスタンスの一覧      [/tr]
  

このinstanceのリンクをたどると,そのクラスのインスタンスの一覧を参照できます。例えば,Stringクラスのインスタンスを見てみましょう(図4)。このインスタンスのリンクでページを移動すると,そのインスタンスのフィールドの情報やスタック・トレースが表示されます(図5)。
  
    [tr]        図4 Stringインスタンスの一覧      [/tr]      [tr]        図5 Stringインスタンスの情報      [/tr]
  

このようにして,アプリケーションのプロファイル情報を参照できます。テキストだけなので,見にくい部分もありますが,それでもjava.hprof.txtを直接参照するよりは圧倒的に簡単です。
EclipseのTPTPやNetBeans Profilerを使ってもいいですが,全く準備なしで使用できるhprofとjhatの組み合わせも便利ですよ。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 14:36:55 | 显示全部楼层
jconsoleをカスタマイズする


「Java SE 6完全攻略」第3回でjconsoleの機能向上を取り上げたのを覚えているでしょうか。この回ではjconsoleの改良点としてGUIを取り上げましたが,拡張されたのはGUI部分だけではありません。jconsoleに独自のビューを追加することも可能になったのです。
そこで,今週はjconsoleをカスタマイズして新しいビューを追加する機能を紹介します。
jconsoleには「メモリ」「スレッド」などのタブがあり,それぞれグラフなどの形式で情報を表示します。表で数値を表示するより,グラフで表示する方が傾向を一目で見られるので便利です。
メモリーやスレッドにはそれぞれ対応するMXBeanがあり,jconsoleはMXBeanが保持する情報を専用のビューで見やすく表示します。
しかし,自作のMBeanやMXBeanなどを専用のビューで表示することはできません。もちろん,MBeanタブの属性の表示でグラフ表示はできますが,決して見やすいものではありませんでした。
標準で提供されるMXBeanの情報参照用に限ってしまえば,jconsoleは使いやすいのですが,汎用のJMXクライアントとしては少し見劣りします。
そこで,Java SE 6ではjconsoleをカスタマイズができるようになりました。標準で提供されるMXBean以外のMBeanやMXBeanに対応したビューを追加できるようになったのです。
jconsoleにタブを追加するjconsoleをカスタマイズするために提供されているのがJConsole APIです。こういう名前が付いていますが,中身はインタフェース一つ,クラス一つというシンプルなAPIです。
ただし,jconsoleにビューを追加するにはSwingとMBeanも必要です。
それでは,さっそく簡単なサンプルを作ってみましょう。
新たにMBeanを作るのは大げさなので,MemoryMXBeanを利用して,ヒープの使用状況を表示するタブを追加することにします。
サンプルはこちらからダウンロードできます: MemoryUsagePlugin.java, MemoryUsageIndicator.java
プラグインの実装jconsoleのカスタマイズはプラグインという形式で行います。プラグインはcom.sun.tools.jconsole.JConsolePluginクラスの派生クラスを作成することで実現できます。JconsolePluginはアブストラクト・クラスで,これを派生したクラスは次の二つのメソッドを実装する必要があります。
  
  • Map<String, JPanel> getTabs()
  • SwingWorker<?, ?> newSwingWorker()
getTabsメソッドは,追加するタブを返すメソッドです。一つのプラグインで複数のタブを作成することができるため,戻り値はMapオブジェクトになっています。Mapオブジェクトのキーがタブに表示する文字列,値がタブで表示を行うJPanelオブジェクトになります。
newSwingWorkerメソッドは,表示の更新を行うSwingWorkerオブジェクトを返すメソッドです。SwingWorkerクラスもJava SE6で導入されたクラスで,非同期にSwingの描画処理を行うためのユーティリティ・クラスです(SwingWorkerクラスも今後,この連載で取り上げる予定です)。
これらのメソッドを実装する以外に,jconsoleとJava VMとの接続,切断などのイベント処理を行う必要があります。
サンプルは二つのクラスで構成されています。MemoryUsagePluginクラスがJConsolePluginクラスを派生させたクラスで,プラグインのメインになるクラスです。もう一つのMemoryUsageIndicatorクラスはJPanelの派生クラスで,表示を行うクラスになります。
まずはMemoryUsagePluginクラスから見ていきましょう。
MemoryUsagePluginクラスのコンストラクタを以下に示します。
  [size=0.9em]        public MemoryUsagePlugin() {
        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);
                    }
                }
            }
        });
    }  
コンストラクタではjconsoleとJava VMの接続,切断などのイベント処理を行いました。このイベント処理にはjava.beans.PropertyChangeEventクラスを使用します。
リスナーはjava.beans.PropertyChangeListenerインタフェースですが,JConsolePluginクラスのリスナーを追加するメソッドがaddContextPropertyChangeListenerメソッドであることを注意してください。
プロパティが変更されたときにコールされるメソッドはpropertyChangeメソッドです。
propertyChangeメソッドでは,まず変化したプロパティを取得し,それがJConsoleContext.CONNECTION_STATE_PROPERTYかどうかをチェックしています。com.sun.tools.jconsole.JConsoleContextクラスはjconsoleとJavaVMのやり取りを行うために使用するクラスです。
変更されたプロパティがCONNECTION_STATE_PROPERTYであった場合は,変更された値を取り出します。この値の型はJConnectionContext.ConnectionStateクラスで,JConsoleContextクラスの内部クラスです。
jconsoleとJava VMが接続されたときにはCONNECTED,接続中の場合はCONNECTING,切断されたらDISCONNECTEDになります。
CONNECTEDの場合,まずgetContextメソッドを使用してJConsoleContextオブジェクトを取得します。次に,getMBeanServerConnectoinメソッドをコールすることで,JavaVMで動作しているMBeanServerとやり取りを行うjavax.management.MBeanServerConnectionオブジェクトを取り出します。そして,表示を行うMemoryUsageIndicatorオブジェクトに引き渡しています。
このサンプルは周期的にヒープの使用状況を更新するわけではないので,CONNECTEDだけを扱っています。
これでイベント処理は終了です。次がgetTabsメソッドです。
          @Override
    public synchronized Map<String, JPanel> getTabs() {
        if (tabs == null) {
            indicator = new MemoryUsageIndicator();
            indicator.setMBeanServerConnection(
                getContext().getMBeanServerConnection());
            
            tabs = new LinkedHashMap<String, JPanel>();
            tabs.put("HeapUsage", indicator);
        }
        
        return tabs;
    }  
getTabsメソッドは,表示を行うクラスを保持するMapオブジェクトを返すためのメソッドです。親クラスで定義されたメソッドなので,@Overrideアノテーションで修飾しておきましょう。前述したように,マップのキーはタブに表示させる文字列,値がJPanelオブジェクトとなります。
このサンプルではJPanelクラスを派生させたMemoryUsageIndicatorクラスを使用しました。
tabsが存在しない場合はMemoryUsageIndicatorオブジェクトを生成し,イベント処理のときと同様にMBeanServerConnectionオブジェクトをセットします。
そして,Mapオブジェクトを生成します。
ここで,LinkedHashMapクラスを使用しているのは,Mapオブジェクトから値を取り出すときの順序を一意に決めることができるためです。この順序はLinkedHashMapオブジェクトに要素を挿入した順序となります。
このサンプルでは値は一つしかないのでほとんど意味はないのですが,タブの並び順にこだわるならばLinkedHashMapクラスを使用しましょう。
最後に残ったのが,newSwingWorkerメソッドです。SwingWorkerクラスは描画のためのクラスなので,SwingWorkerオブジェクトは描画を担当するMemoryUsageIndicatorクラスに委譲することにしました。
          @Override
    public SwingWorker<?,?> newSwingWorker() {
        return indicator.newSwingWorker();   
    }  
表示部の実装それでは表示を行うMemoryUsageIndicatorクラスを実装していきます。
MemoryUsageIndicatorクラスは前述したようにJPanelの派生クラスとして実装しました。
表示に関してはサンプルということもあり,かなり簡略化しています。MemoryUsageクラスのused,committed,maxの表示にはJLabelクラスを使用しています。
          public MemoryUsageIndicator() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(3, 2, 5, 5));
  
        JLabel usedLabel = new JLabel("Used");
        panel.add(usedLabel);
        usedValue = new JLabel("0");
        panel.add(usedValue);
  
        JLabel committedLabel = new JLabel("Committed");
        panel.add(committedLabel);
        committedValue = new JLabel("0");
        panel.add(committedValue);
  
        JLabel maxLabel = new JLabel("Max");
        panel.add(maxLabel);
        maxValue = new JLabel("0");
        panel.add(maxValue);
  
        add(panel);
    }  
ヒープの使用状況を表示するにはMemoryMXBeanから情報を取得しなければなりません。MBeanServerConnectionクラスを使用してMemoryMXBeanのプロパティを取得することもできるのですが,ここではもっと簡単な方法を使用します。
MXBeanのプロキシを使用する方法です。プロキシを使うと,MXBeanの実体はリモートにあるにもかかわらず,それを意識することなく,通常のオブジェクトのように使うことができます。
          public void setMBeanServerConnection(
                MBeanServerConnection connection) {
        try {
            memoryMXBean
                = ManagementFactory.newPlatformMXBeanProxy(
                    connection,
                    ManagementFactory.MEMORY_MXBEAN_NAME,
                    MemoryMXBean.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }  
MXBeanのプロキシを生成するには,java.lang.management.ManagementFactoryクラスのnewPlatformMXBeanメソッドを使用します。このメソッドの第1引数はMBeanServerConnectionオブジェクト,第2引数がMXBeanの名前になります。MXBeanの名前はManagementFactoryクラスが定数として定義しているものを使用します。最後の引数がプロキシを作成するMXBeanのClassオブジェクトになります。
たいていの場合,このようなメソッドにはダウンキャストが必要なのですが,このメソッドはダウンキャストが必要ありません。というのも,ジェネリクスを使用しているからです。
MXBeanでないMBeanの場合はjavax.management.JMXクラスのnewMBeanProxyメソッドを使用します。
さて,MemoryMXBeanが取得できたので,あとはGUIの表示更新の部分です。表示更新には前述したようにSwingWorkerクラスを使用します。
          class Worker extends SwingWorker<MemoryUsage,Object> {
        @Override
        public MemoryUsage doInBackground() {
            return getMemoryUsage();
        }

        @Override
        protected void done() {
            try {
                // doInBackgroundで取得したMemoryUsageを取得
                MemoryUsage usage = get();

                usedValue.setText(
                    String.format("%d", usage.getUsed()));
                committedValue.setText(
                    String.format("%d", usage.getCommitted()));
                maxValue.setText(
                    String.format("%d", usage.getMax()));
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            } catch (ExecutionException ex) {
                ex.printStackTrace();
            }
        }
    }

    private MemoryUsage getMemoryUsage() {
        return memoryMXBean.getHeapMemoryUsage();
    }

    public SwingWorker<?,?> newSwingWorker() {
        return new Worker();
    }  
SwingWorkerクラスはabstractクラスで,doInBackgroundメソッドとdoneメソッドをオーバライドする必要があります。
SwingWorkerクラスの定義では二つの型をジェネリクスで指定しています。前者はdoInBackgroundメソッドとgetメソッドの戻り値の型になります。後者はここでは使用しません。今後,SwingWorkerの解説を予定しているので,詳しい説明はそのときに行います。
doInBackgroundメソッドでMemoryMXBeanからヒープの使用状況を表すMemoryUsageオブジェクトを取得します。このMemoryUsageオブジェクトはgetメソッドで取得できます。
実際に描画処理を記述するのがdoneメソッドです。まず,getメソッドを使用して,doInBackgroundメソッドで取得したMemoryUsageオブジェクトを取得します。
あとは,MemoryUsageオブジェクトからused,committed,maxの値を取得して,JLabelオブジェクトにセットするだけです。ここでは簡易フォーマットのためにString#formatメソッドを使用しました。String#formatメソッドはC言語のprintfと同様のフォーマットが可能です。
このSwingWorkerクラスを派生したWorkerクラスを生成して,MemoryUsagePluginオブジェクトに引き渡すための,newSwingWorkerメソッドを記述します。
プラグインのコンパイル,実行プログラムができたので,コンパイルしましょう。ここで使用したcom.sun.tools.jconsole.JConsolePluginなどのクラスは[JAVA_HOME]\lib\jconsole.jarで定義されています。したがって,クラスパスにはこのJARファイルを指定します。
Windows XPでの例を示します。JAVA_HOME環境変数はC:\Program Files\Java\jdk1.6.0を示しています。
  [size=0.9em]    C:\temp>java -cp "%JAVA_HOME%\lib\jconsole.jar";. MemoryUsagePlaugin.java  
コンパイルできたので,次にJARファイルにまとめます。ただ,単にJARファイルにまとめただけでは,どのクラスがプラグインのメインクラスなのかわかりません。そこで,com.sun.tools.jconsole.JConsolePluginというファイルを用意します。すごい長ったらしいファイル名ですが,これで一つのファイル名です。
com.sun.tools.jconsole.JConsolePluginファイルにはプラグインのメインクラスを記述します。
      MemoryUsagePlugin  
そして,com.sun.tools.jconsole.JConsolePluginファイルをMETA_INF\servicesディレクトリの配置します。ここではc:\tempにソースなどが置いてあるので,c:\temp\META_INF\services\com.sun.tools.jconsole.JConsolePluginになります。
JARファイルにはこのファイルも一緒に含めるようにします。
      C:\temp>jar cvf memoryusageplugin.jar MemoryUsage*.class
META_INF\service\com.sun.tools.jconsole.JConsolePlugin
(実際には1行)  
このようにして特定のクラスを指定する方法は,java.util.ServiceLoaderクラスを使用したものです。このクラスもJava SE 6から導入されています。
やっとJARファイルを作ることができました。最後にこれをjconsoleに組み込みましょう。JARファイルを指定するには-pluginpathオプションで指定します。
      C:\temp>jconsole -pluginpath memoryusageplugin.jar  
何か問題があった場合は問題個所を示したダイアログが表示されるので,それを見て修正を加えてください。プラグインが正しくできていれば,[HeapUsage]というタブが表示されるはずです(図1)。
  
    [tr]        図1 Heap Usageタブ      [/tr]
  

サンプルということで,表示はかなりしょぼくなっています。もっとも表示はSwingで記述できるので,ターゲットになるMBean/MXBeanに応じていろいろと見せ方を工夫できるはずです。
今回は既存のMemoryMXBeanを例に取りましたが,自作のMBean/MXBeanを扱うことも可能です。アプリケーションに応じてプラグインを作成すれば,あたかもそのアプリケーション専用の管理ツールができあがることになります。
複数のMBeanの情報をまとめて表示することももちろん可能です。ぜひ,自分なりの管理ツールを作り上げてみてください。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 14:38:18 | 显示全部楼层
自分で作るMXBean


今週もまた管理に関する機能です。管理や保守に関する機能はまだまだ当分続きますので,おつきあいください。
MXBeanが便利であることは何度も言及してきました。もちろん,筆者もよく使用しています。しかし,不満な点もあります。MXBeanを自分で作ることができないという点です。
もちろん,MBeanであればいくらでも自作できます。Jakarta Commons Modelerなどを使えば,簡単にMBeanを作成することが可能です。詳しくは「Model MBean - Commons Modelerコンポーネントを使用して」をご覧ください。
ただ,やっぱりMXBeanを作りたいのです。Java SE 6ではようやくMXBeanが自作できるようになりました。さっそくこの機能を紹介していきましょう。
MXBeanの定義一般的に,MBeanを示すインタフェース名は名前の最後にMBeanが付いていなければなりません。これは命名規約によるものです。
MXBeanでも同様にMXBeanをインタフェース名の最後に付加します。これで,このインタフェースがMXBeanであることを指定できます。
ところが,これ以外にもMXBeanであることを示す方法があります。アノテーションを使用する方法です。アノテーションを使えば,インタフェース名の最後がMXBeanでなくてもMXBeanとして扱うことが可能です。
例えば,以下の定義はすべてMXBeanとして扱うことができます。
      public interface FooMXBean {...}
       
@MXBean       
public interface Foo {...}

@MXBean(true)
public interface Foo {...}  
逆に以下のインタフェースはMXBeanとしては扱えません。
      public interface Foo {...}
       
@MXBean(false)       
public interface Foo {...}

@MXBean(false)
public interface FooMXBean {...}  
最後の例は,インタフェース名にMXBeanと付いていますが,それでもMXBeanとして扱うことはできません。
MXBeanを示すインタフェースができたら,あとはStandard MBeanと同様にMXBeanを実装します。
MXBeanの実装 MXBeanを扱うために簡単なサンプルを作ってみました。テキスト・フィールドとボタンが二つあるアプリケーションです。
テキスト・フィールドには数字が表示されており,これがカウンタを表しています。Incrementボタンを押すと,カウンタが増加します。10までカウントが進むとIncrementボタンを押せないようにしました。
Resetボタンを押すと,カウンタがリセットされ,押せなかったIncrementボタンが再び押せるようになります。  
  
  [tr]        図1 Counter       [/tr]
  

このサンプルを,MXBeanを使用してカウンタの値を取得できるようにし,またリセットできるようにしてみます。
サンプルのダウンロードはこちらから:Counter.javaCounterWatcher.javaCounterWatcherImpl.java
Counterクラスがもともとのアプリケーションです。MXBeanの定義はCounterWatcherインタフェースで行います。それをインプリメントしているのがCounterWatcherImplクラスです。
カウンタの値を取得できるように,CounterクラスにはgetCountメソッドを定義しました。また,リセットにはResetボタンを押されたときにコールされるresetCounterメソッドを使用します。
準備はこれでおしまい。実際にMXBeanを作っていきましょう。
MXBeanの定義はCounterWatcherインタフェースで行います。
      import javax.management.MXBean;

@MXBean
public interface CounterWatcher {
    public int getCount();
    public void reset();
}  
getCountメソッドが現在のカウンタの値を取得するメソッド,resetメソッドがカウンタをリセットするメソッドです。MXBeanであることは@MXBeanアノテーションで指定しています。
CounterWatcherインタフェースをインプリメントしているのがCounterWatcherImplクラスです。
  [size=0.9em]    public class CounterWatcherImpl implements CounterWatcher {
    private static final String NAME = "net.javainthebox.counter:type=Counter";

    private Counter counter;

    public CounterWatcherImpl(Counter counter) {
        this.counter = counter;
    }  
NAME定数は,MBeanServerに登録するときのMXBeanの名前に使用します。そして,コンストラクタでターゲットとなるCounterオブジェクトを設定しています。
getCountメソッドもresetメソッドもCounterクラスに処理を委譲しています。
          public int getCount() {
        return counter.getCount();
    }

    public void reset() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                counter.resetCounter();
            }
        });
    }  
resetメソッドでSwingUtilities#invokeLaterメソッドを使用しているのは,Counter#resetメソッドの中で描画処理を行っているからです。Swingはシングルスレッドで設計されており,他のスレッドから描画処理を行うことができません。他のスレッドから描画処理を行うには,ここで記述したようにSwingUtilitiesクラスを使用します。
最後にmainメソッドです。
  [size=0.9em]        public static void main(String[] args) {
        Counter counter = new Counter();
        CounterWatcher watcher = new CounterWatcherImpl(counter);
         
        try {
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            ObjectName name = new ObjectName(NAME);
            server.registerMBean(watcher, name);
        } catch (MalformedObjectNameException ex) {
            ex.printStackTrace();
        } catch (InstanceAlreadyExistsException ex) {
            ex.printStackTrace();
        } catch (MBeanRegistrationException ex) {
            ex.printStackTrace();
        } catch (NotCompliantMBeanException ex) {
            ex.printStackTrace();
        }
    }  
ターゲットになるCounterオブジェクトを生成し,次にCounterWatcherImplオブジェクトを生成します。
次に行うのがMXBeanの登録です。MXBeanの登録はMBeanServerオブジェクトに対して行います。MBeanServerオブジェクトは,ManagementFactory#getPlatformMBeanServerメソッドで取得します。このメソッドで得られるMBeanServerオブジェクトは,他の標準で提供されるMXBeanがすでに登録されているMBeanServerオブジェクトになります。
MBeanServerオブジェクトにMXBeanを登録するやり方は,MBeanを登録するのと全く同じです。ObjectNameクラスでMXBeanの名前を指定し,MBeanServer#registerMBeanメソッドで登録します。
jconsoleで確認するソースができたら,コンパイルして実行してみましょう。起動したら,jconsoleでCounterWatcherImplにアタッチし,[MBean]タグを見てください。ちゃんとCounterがあることを確認できるはずです。
  
    [tr]        図2 jconsoleで確認       [/tr]
  

属性のCountを見てみましょう。ちゃんとCounterで表示されている値と同じものが表示されています。
  
    [tr]        図3 カウンタを参照       [/tr]
  

resetを実行すると,Counterの値がリセットされます。これもやってみてください。
MXBeanは,アノテーションを使うことで簡単に定義できます。また,MXBeanの実装はStandard MBeanとほとんど同じです。ぜひ,自分なりのMXBeanを作ってみてくださいね。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 14:40:34 | 显示全部楼层
MBeanのメタデータを記述する- Descriptor


先週はMXBeanを作成しました。今週はその続きです。
jconsoleは属性値が数値の場合,グラフを表示できます。例えば,先週作成したCounterWatcherでは,図1のようになります。
  
    [tr]        図1 カウンタを参照       [/tr]
  

Counterは0から10の値を取るのですが,このグラフには最大値が10であることは考慮されていません。
属性の取りうる値や単位など,MBeanのメタデータに関してはMBeanInfoクラスやMBeanAttributeInfoクラスなどに記述していました。しかし,それが使いやすいかといえば,かなり疑問が残ります。
Java SE 6ではメタデータをDescriporインタフェースを使用して表せます。もともとModelMBeanだけはDescriptorインタフェースを使用することができたのですが,それがすべてのMBean/MXBeanで使用できるように拡張されたのです。
しかし,Descriptorインタフェースだけではまだまだ使うのが大変です。そこで,取り入れられたのが@DescriptorKeyアノテーションです。
@DescriptorKeyアノテーションを使ってみる@DescriptorKeyアノテーションはメタアノテーションです。メタアノーテションとは,アノテーションを修飾するためのアノテーションのことです。
つまり,MBean/MXBeanで@DescriptorKeyアノテーションを使うには,@DescriptorKeyアノテーションを使用したアノテーションを作成する必要があります。
ここでは,最大値と最小値を表すための@Rangeというアノテーションを作成してみましょう。
      import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.management.DescriptorKey;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)

public @interface Range {
    @DescriptorKey("maxValue")
    int max();
  
    @DescriptorKey("minValue")
    int min();
}  
アノテーションを作成するには,いくつかのメタアノテーションで修飾しなければなりません。
@DocumentedはJavadocなどでドキュメント化されることを示しています。@Targetは,ここで作成するアノテーションがどの要素に対して使用可能であるかを示します。ここではMETHODを指定しているので,@Rangeはメソッドに対して使用できることを示しています。@Retentionはアノテーションが適用される範囲を示します。RUNTIMEの場合は,実行時に@Rangeが使用できることを示しています。
@Rangeアノテーションが定義するプロパティはmaxとminです。それぞれを@DescriptorKeyアノテーションで修飾します。
@DescriptorKeyアノテーションの引数は,Descriptorで表せるプロパティを示しています。
Descriptorで表せるプロパティには,ここで使用したmaxValueやminValue以外に,defaultValueやunitsなどがあります。主なプロパティを表1に示しました。表1に示した以外のプロパティはDescriptorのJavadocを参照してください。
  
    表1 DescriptorKeyで使用できる主なプロパティ
            名前        型        説明                    defaultValue        Object        プロパティなどのデフォルト値                    maxValue        Object        プロパティなどの最大値                    minValue        Object        プロパティなどの最小値                    originalType        String        CompositeDataに変換されるプロパティやオートボクシングで変換される場合,元の型を記述する                    units        String        プロパティなどの単位        

@Rangeアノテーションが作成できたので,先週作成したCounterWatcherインタフェースを@Rangeで修飾してみましょう。
      @MXBean
public interface CounterWatcher {
    @Range(max=10, min=0)
    public int getCount();
       
    public void reest();
}  
getCountメソッドを@Rangeアノテーションで修飾します。このコードではmaxが10,minが0になります。これでコードの修正はおしまいです。直接,Descriptorインタフェースを使う場合に比べて,とても簡単にプロパティを指定できます。
それでは,jconsoleで試してみましょう。残念ながら,グラフにはmaxValueとminValueは反映されません。しかし,属性の記述子の部分を見てみると,ちゃんとmaxValueとminValueが設定されていることを確認できます。図2は@DescriptorKeyを使わなかった場合,図3は使用した場合です。
  
    [tr]        図2 @DescriptorKeyを使用しない場合       [/tr]      [tr]        図3 @DescriptorKeyを使用した場合      [/tr]
  

ここで設定したmaxValueとminValueは,MBeanServerConnectionクラスを使用して取得できます。したがって,第6回で解説したようにjconsoleにCounterWatcher用のタブを作成するのであれば,これらの値を使用できます。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 14:41:41 | 显示全部楼层
複合要素に対するモニタリング


今月でとうとうこの連載も1周年を迎えることができました。筆者にとってはじめての連載ということで,連載前は本当に書けるのだろうかとかなり心配したのですが,とりあえず1年間無事に続けることができました。
これもひとえに読者のみなさまのおかげです。これからも最新のJavaの情報をお届けできるようにしていきたいと思います。
というわけで,本題に入りましょう。
今週も,Java SE 6のソフトウエア管理に関する新機能です。
属性を監視するモニターこの連載でJMXを解説したときに,いくつか取りあげなかった機能があります。そのうちの一つがモニターです。
モニターは特殊なMBeanで,他のMBean/MXBeanの属性を定期的に調べることができます。それぞれにルールを設定でき,そのルールに応じてイベント(JMXの場合,ノティフィケーションといいます)を発生させることができます。
標準では3種類のモニターを使用できます。いずれもjavax.management.monitorパッケージで定義されており,javax.management.monitor.Monitorクラスの派生クラスになります。
   表1 標準で提供されるモニター
                クラス名          説明                          CounterMonitor          数値を監視するモニター
          しきい値を超えるとノティフィケーションを発生する                          GaugeMonitor          数値を監視するモニター
          上限と下限の二つのしきい値を設定できる                          StringMonitor          文字列を監視するモニター         
CounterMonitorクラスとGaugeMonitorクラスは数値を監視するためのモニターです。もう一つのStringMonitorクラスは文字列を監視します。
CounterMonitorは,図1に示したように,設定したしきい値を超えるとノティフィケーションを発生させるモニターです。オフセットを指定しておくと,しきい値を超えたあとオフセット分だけ値が増加したときにノティフィケーションを発生します。例えば,しきい値を5,オフセットを3に指定した場合,モニターする値が5,8,11...のときにノティフィケーションが発生します。
また,カウントをリセットするには,モジュラスというプロパティを指定しておきます。カウンタがモジュラスで指定した値になったとき,0にリセットされます。
GaugeMonitorクラスは図2に示したように,上限と下限を設定できるモニターです。上限を超えるか,下限を下回ったときにノティフィケーションが発生します。
最後のStringMonitorは監視する対象が文字列になります。監視対象の文字列が設定された文字列と同じになったときノティフィケーションを発生させるモードと,異なったときにノティフィケーションを発生させるモードがあります。
  
    [tr]        図1 CounterMonitor      [/tr]      [tr]        図2 GaugeMonitor      [/tr]
  

モニターを使ってみるモニターを理解するには使ってみるのが一番です。ここでは第7回「自分で作るMXBean」で使用したサンプルにCounterMonitorクラスを追加してみましょう。
サンプルはここからダウンロードできます:Counter.javaCounterWatcher.javaCounterWatcherImpl.java
コードを変更するのはCoungterWatcherImpleクラスだけです。mainメソッドでMXBeanを作成しているので,そこでCounterMonitorクラスも設定します。
  [size=0.9em]    private static final String MONITOR_NAME
        = "net.javainthebox.counter:type=CounterMonitor";

public static void main(String[] args) {
    Counter counter = new Counter();
    CounterWatcher watcher = new CounterWatcherImpl(counter);

    try {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName name = new ObjectName(NAME);
        server.registerMBean(watcher, name);

        // 1. カウンタ・モニターを生成し,MBeanServerに登録
        CounterMonitor counterMonitor = new CounterMonitor();
        ObjectName monitorName = new ObjectName(MONITOR_NAME);
        server.registerMBean(counterMonitor, monitorName);

        // 2. カウンタ・モニターにcounterのcountを登録
        counterMonitor.addObservedObject(name);
        counterMonitor.setObservedAttribute("Count");

        // 3. ノティフィケーションを発生可能に設定
        counterMonitor.setNotify(true);

        // 4. 初期しきい値を3に設定
        counterMonitor.setInitThreshold(3);

        // 5. オフセットを2に設定
        counterMonitor.setOffset(2);

        // 6. モジュラスを10に設定
        counterMonitor.setModulus(10);

        // 7. ポーリングの時間を100msに設定
        counterMonitor.setGranularityPeriod(100L);

        // 8. モニターの開始
        counterMonitor.start();

    } catch (MalformedObjectNameException ex) {
        ex.printStackTrace();
    } catch (InstanceAlreadyExistsException ex) {
        ex.printStackTrace();
    } catch (MBeanRegistrationException ex) {
        ex.printStackTrace();
    } catch (NotCompliantMBeanException ex) {
        ex.printStackTrace();
    }
}
CounterMonitorクラスは,単純にnewで生成できます。CounterMonitorクラスもMBeanなので,MBeanServerオブジェクトへの登録は他のMBean/MXBeanと同様です。
2.で監視するMBean/MXBeanをaddObservedObjectメソッドで指定し,setObservedAttributeメソッドを使用して属性を指定します。しきい値などのプロパティを設定しているのが,3.から7.のステップです。
最後にstartメソッドでCounterMonitorオブジェクトを起動します。
さっそく起動して,jconsoleで確かめてみましょう。
MBeanタブでMBeanの一覧を見てみると,CounterMonitorがあることを確認できます。図3はCounterMonitorのノティフィケーションに関するビューを示しています。MBeanからのノティフィケーションを受け取るには,[登録]ボタンをクリックします。
CounterMonitorのしきい値は3に設定したので,カウンタが3になるとノティフィケーションが発生します(図4)。オフセットは2に設定したので,カウンタが3,5,7,9でノティフィケーションが発生します。
CounterMonitorの内部カウンタは一律に増加するだけで,減少することはありません。内部カウンタをリセットさせるには,モジュラスを設定しておきます。内部カウンタがモジュラスに到達すると,0にリセットされます。
このサンプルではモジュラスを10にしてあります。このため,カウンタが10まで到達し,リセットされると,CounterMonitorも再びノティフィケーションを発生します(図5)。
  
    [tr]        図3 CounterMonitorのノティフィケーション      [/tr]      [tr]        図4 ノティフィケーションの通知      [/tr]      [tr]        図5 ノティフィケーションの通知      [/tr]
  

オフセットもモジュラスも設定しない場合は,しきい値に達すると常にノティフィケーションを発生するようになります。
MBean/MXBeanにノティフィケーションを発生させる機能を実装しなくても,このようにモニターを使うことでノティフィケーションを発生させることができます。
しかし,いくつかの制約もあります。制約の一つは,複合要素の属性に対するモニターができないことです。
例えば,MemoryMXBeanのヒープ使用量を監視したいとします。ヒープ使用量はMemoryMXBeanが直接保持せず,MemoryUsageクラスのused属性として保持されています。このような場合,モニターはMemoryUsageクラスにアクセスする手段を持たないため,監視できませんでした。
Java SE 6ではこの制約が取り払われました。複合要素でも監視が可能になったのです。
複合要素の監視モニターで属性を監視するには,前述したようにMonitor#setObservedAttributeメソッドで指定します。CounterWatcherImplクラスでは次のように記述しました。
      counterMonitor.setObservedAttribute("Count");  
CounterWatcherのCount属性を表しています。つまり,この値を取得するには,getCountメソッドを使用します。
複合要素の場合,次のように記述します。
      counterMonitor.setObservedAttribute("HeapMemoryUsage.used");  
この記述はMemoryMXBeanのHeapMemoryUsage属性のused属性を示しています。つまり,複合要素の場合は「.」(ピリオド)で続けて属性を記述するようにします。
この場合,MemoryMXBeanのgetHeapMemoryUsageメソッドがコールされ,その次にMemoryUsage#getUsedメソッドがコールされます。
ModelMBeanで使用されるCompositeDataクラスを使用して複数の属性を保持するときには,ピリオドの後の文字列をCompositeData#getメソッドの引数にしてコールします。
また,xxx属性のyyy属性のzzz属性のような場合には,同じくピリオドで区切ってxxx.yyy.zzzと記述します。
このように複合要素の場合でも,特別なメソッドを必要とせず,普通の属性を監視するのと全く同じ方法で監視できるのです。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 14:42:29 | 显示全部楼层
オンデマンドアタッチを実現するAttach API


「Java SE 6完全攻略」の第2回でオンデマンドアタッチを紹介しました。起動時に何も指定しなくても,必要なときにjconsoleでJava VMにアタッチできるという機能です。
それにしても,オンデマンドアタッチって,どうやって実現させているか不思議ではないですか?
筆者は夜も眠れぬぐらい不思議に思っていたのです。そんなある日,あらためてJava SE 6のドキュメントをつらつら眺めていると,Attach APIという聞き慣れないAPIを見つけたのです。
そう,このAttach APIがオンデマンドアタッチを実現するためのAPIだったのです。
Attach APIの正体AttachAPIはcom.sun.tools.attachパッケージとcom.sun.tools.attach.spiパッケージで定義されている四つのクラスから構成されています。しかし,主に使うのはcomsun.tools.attach.VirtualMachineクラスだけです。
また,パッケージ名がcom.sunではじまっていることからわかるように,標準のAPIではありません。SunのHotSpot VMにだけ適用できるAPIになっています。
VirtualMachineクラスが提供している機能はそれほど多くありません。主な機能を挙げてみましょう。
  
  • VMの一覧を取得
  • VMへのアタッチ/デタッチ
  • 環境変数の取得
  • Instrumentation APIを使用するエージェントのロード
  • Java Virtual Machine Tool Interface(JVMTI)を使用するエージェントのロード
jconsoleを起動したときのことを思いだしてください。一番はじめに起動しているVMの一覧が表示されますね。このVMの一覧を取得するのにAttach APIを利用しています。
そして,VMにアタッチしたあとに,VMにエージェントを送り込みます。
エージェントにはいろいろな意味があり,使う人によって指すものが異なることもよくあります。ここでの「エージェント」は,Javaのアプリケーションとは独立して動作するコンポーネント,という意味です。
このエージェントには2種類あります。一方が,Instrumentation APIで使用するエージェント。これはJavaで記述されています。もう一方が,JVMTIを使用するエージェント。こちらはC/C++で記述されています。
Instrumentation APIを利用したエージェントは,mainメソッドがコールされる前に起動し,クラスファイルを操作したりすることができます。
このエージェントはVirtualMachineクラスのloadAgentメソッドを使用して,ロードします。
JVMTIのエージェントは,デバッグとプロファイリングに使います。このエージェントはVirtualMachineクラスのloadAgentLibraryメソッド,もしくはloadAgentPathメソッドを使用してロードします。
jconsoleのオンデマンドアタッチは二つのエージェントのうち,Instrumentation APIを使用したエージェントによるものです。
それでは,オンデマンドアタッチと同じことをここで実現してみましょう。
オンデマンドアタッチを実現するせっかくなので,jconsoleと同じようにJMXとMXBeanを使って,ヒープの使用量を調べてみましょう。
ソースコードはこちらからダウンロードできます:AttachSample.javaAttachAgent.javaMANIFEST.MF
それでは,まずAttach APIを使用して,起動しているVMの一覧を出力してみます。これを行っているのが,AttachSampleのデフォルトコンストラクタです。
  public AttachSample() {
     List<VirtualMachineDescriptor> vms = VirtualMachine.list();

     System.out.println("PID   VM");
     for(VirtualMachineDescriptor vm: vms) {
         System.out.printf("%4s: %s%n",
                           vm.id(), vm.displayName());
     }
}  
起動しているVMの一覧を取得するには,VirtualMachineクラスのlistメソッドを使用します。戻り値はVirtualMachineDescriptorオブジェクトのリストになります。
VirtualMachineDescriptorクラスは,その名前のとおり,VMの情報を保持するクラスです。上記のコードではVMのIDと名前を出力しています。IDの型はStringです。WindowsやUNIXでは,VMのIDとしてプロセスIDが使われます。
次はいよいよVMにアタッチしてみましょう。
アタッチするには,VirtualMachineクラスのstaticなattachメソッドを使用します。attachメソッドの引数はVMのID,もしくはVirtualMachineDescriptorオブジェクトです。下に示すコードではIDを使用しました。
  private final static String AGENT_PATH = "C:\\temp\\agent.jar";
                          
public AttachSample(String pid) {
    try {
        // VMにアタッチ
        VirtualMachine vm = VirtualMachine.attach(pid);

        // エージェントをVMにロードする
        vm.loadAgent(AGENT_PATH, null);

        // VMからデタッチ
        vm.detach();

        connect();
    } catch (AttachNotSupportedException ex) {
        ex.printStackTrace();
    } catch (AgentLoadException ex) {
        ex.printStackTrace();
    } catch (AgentInitializationException ex) {
        ex.printStackTrace();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}  
次のloadAgentメソッドでVMにエージェントをロードしています。loadAgentメソッドの第1引数はロードするエージェントを含むJARファイルの場所,第2引数がエージェントに与える引数になります。
そして,VMから切断するためにdetachメソッドをコールします。
ロードするエージェントに関しては後述します。
サンプルとして作成したエージェントは,JMX Remoteを使用してリモートからアクセスできるようにしています。そこで,connectメソッドでアクセスしてみます。
  private final static String JMX_URL
          = "service:jmx:rmi:///jndi/rmi://localhost/jmx";
                          
private void connect() {
    try {
        // ロードしたエージェントがオープンしたMBeanServerに接続
        JMXServiceURL url = new JMXServiceURL(JMX_URL);
        JMXConnector connector = JMXConnectorFactory.connect(url);
        MBeanServerConnection connection
            = connector.getMBeanServerConnection();

        // MBeanServerからMXBeanを取得
        MemoryMXBean memoryMXBean
            = ManagementFactory.newPlatformMXBeanProxy(
                connection,
                ManagementFactory.MEMORY_MXBEAN_NAME,
                MemoryMXBean.class);

        // ヒープの使用量を出力
        MemoryUsage usage = memoryMXBean.getHeapMemoryUsage();
        System.out.println(usage);

        connector.close();
    } catch (MalformedURLException ex) {
        ex.printStackTrace();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}  
JMXRemoteを使用してVMにアクセスするには,次のような手順を踏みます。
  
  • JMXServiceURLクラスを使用してアクセスするJMXのURLを表す
  • JMXServiceURLオブジェクトを引数にして,JMXConnectorFactory#connectメソッドをコールする。connectメソッドの戻り値はJMXConnectorオブジェクトになる
  • JMXConnectorクラスのgetMBeanServerConnectionメソッドを用いて,MBeanServerとの接続を表すMBeanServerConncetionオブジェクトを取得する
MBeanServerConnectionオブジェクトが取得できたら,第6回のjconsoleのプラグインで使用した方法と同様にMXBeanのプロキシを生成します。あとは必要な情報をMXBeanから取得するだけです。
最後にJMXConnectorオブジェクトのcloseメソッドをコールして,切断します。
エージェントの作成さて,VMに送り込むエージェントを作成しましょう。
注意すべき点があります。パッケージ宣言をしていないクラスはロードできないので,かならずパッケージを設定するようにしましょう。
エージェントに必要なのは,premainメソッドとagentmainメソッドです。
起動時に実行するエージェントの場合はpremainメソッドがコールされます。起動時に実行するエージェントについては,筆者のWebサイトにある「J2SE5.0虎の穴」のInstrument APIの解説をご参照ください。
VMが起動中にロードされた場合,agentmainメソッドがコールされます。
ここではpremainメソッドはagentmainメソッドをコールしているだけです。
  [size=0.9em]public class AttachAgent {
    public static final String SERVICE_URL
        = "service:jmx:rmi:///jndi/rmi://localhost/jmx";

    // mainメソッドがコールされる前に呼ばれるメソッド
    public static void premain(String args) throws Exception {
        agentmain(args);
    }

    // VM起動後に,エージェントがロードされたときに,
    // コールされるメソッド
    public static void agentmain(String args) throws Exception {
        try {
            // MXBeanを登録してあるMBeanServerを取得
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();

            // JMX Remoteを使用したリモート接続の設定
            JMXServiceURL url = new JMXServiceURL(SERVICE_URL);
            JMXConnectorServer connector
                = JMXConnectorServerFactory.newJMXConnectorServer(
                    url, null, server);

            // コネクタの開始
            connector.start();
        } catch (MalformedURLException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}  
JMX Remoteを使ったアクセスができるようにするにはJMXConnectorServerオブジェクトを使用します。
まずはアクセスするためのMBeanServerオブジェクトを生成します。ここではMXBeanが登録されているMBeanServerオブジェクトを使用します。
アクセスを行うためのURLは先ほど記述したものと同じです。これをJMXServiceURLクラスで表します。
JMXConnectorServerオブジェクトはJMXConnectorFactory#newJMXConncetorServerメソッドを使用して生成します。生成できたらstartメソッドでアクセスできるようにします。
これでエージェントの本体はできました。しかし,これだけではまだ足りません。
今回のサンプルは単純なので,一つのクラスしかありませんが,エージェントが複数のクラスから構成されている場合を考えてみましょう。そのような場合,VMにはどのクラスのagentmainメソッドをコールすればいいかわかりません。
したがって,agentmainメソッドを持つクラスを指定する必要があります。これにはJARファイルのマニフェスト・ファイルを利用します。
  Manifest-Version: 1.0
Premain-Class: net.javainthebox.attach.AttachAgent
Agent-Class: net.javainthebox.attach.AttachAgent  
Premain-Classにpremainメソッドを持つクラス,Agent-Classにagentmainメソッドを持つクラスを記述します。ここで作成したエージェントはpremainメソッドとagentmainメソッドの両方を持つので,両方とも同じAttachAgentクラスを記述しています。
コンパイルと実行さて,ソースコードができたのでコンパイルしましょう。
エージェントはそのままコンパイルできますが,Attach APIを使用しているAttachSampleクラスはそのままではコンパイルできません。
というのもAttach APIは[JDK_HOME]/lib/tools.jarで定義されているからです。
  [size=0.7em]C:\temp>javac -cp "C:\Program Files\Java\jdk1.6.0\lib\tools.jar";. AttachSample.java  
次にエージェントのJARファイルを作成しましょう。MANIFEST.MFファイルはカレント・ディレクトリにあるとします。また,JARファイルの名前はagent.jarとしましょう。
AttachAgentクラスはnet.javainthebox.attachパッケージなので,次のようにjarコマンドを実行します。
  [size=0.8em]C:\temp>jar cvmf MANIFEST.MF agent.jar net\javainthebox\attach\*.class  
コンパイルできたので,実行してみましょう。
JMX Remoteは通信にRMIを使用するので,事前にrmiregistryを実行しておく必要があります。えっ,jconsoleを使うときはrmiregistryなんか実行しないよ,と思われる方も多いでしょう。
そうなんです。rmiregistryはアプリケーション中で実行させることもできるのです。しかし,ここでは簡単化のために省略しました。
興味のある方は,sun.management.Agentクラスを調べてみてください。このクラスがjconsoleによってロードされるエージェントです。
それでは早速実行してみましょう。まずは引数なしでVMの一覧を出力してみます。
  [size=0.8em]C:\temp>java -cp "c:\Program Files\Java\jdk1.6.0\lib\tools.jar";. AttachSample
PID   VM
1132: Java2Demo.jar
3052: sun.rmi.registry.RegistryImpl
1272: AttachSample

C:\temp>  
Java2Demoが動作しているようなので,これにアタッチしてみましょう。
  [size=0.7em]C:\temp>java -cp "c:\Program Files\Java\jdk1.6.0\lib\tools.jar";. AttachSample 1132
init = 0(0K) used = 8934856(8725K) committed = 14352384(14016K) max = 66650112(65088K)

C:\temp>  
ちゃんとアタッチして,ヒープの使用量を出力できました。
このように簡単にエージェントをリモートからVMに送り込むことができます。しかし,勝手にエージェントが送り込まれたら,ちょっと怖いですね。
ここで,jconsoleでオンデマンドアタッチをするときの制限を思いだしてください。そう,同一マシン上で,同一ユーザーだけがjconsoleでアタッチできます。
これはAttach APIの制限によっています。つまり,Attach APIでエージェントをロードする場合,同一マシン,かつ同一ユーザーに限られるというわけです。
今回は,管理のためにAttach APIを使いましたが,その他の用途にもエージェントを使うことはできそうです。JVMTIのエージェントも使用可能なので,パフォーマンスチューニングなどにも活用できますよ。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 14:43:23 | 显示全部楼层
コンポーネントのロードを行うServiceLoader


先週のAttach APIは,どちらかというと裏方で使うAPIでした。今週解説するServiceLoaderクラスも,縁の下の力持ち的なクラスです。
第6回でjconsoleのプラグインを作成したときに,META-INFディレクリと,META-INFディレクトリの下にservicesディレクトリを作成したことを覚えているでしょうか。
なんで,こんなことするのだろうと不思議に思った方も多いかもしれません。これがjava.util.ServiceLoaderクラスを使ったコンポーネントのロードなのです。
コンポーネントをロードするさっそく,サンプルを作って,確かめてみましょう。
サンプルはこちらからダウンロードできます:HowdyTalky.javaHowdyTalkyLoader.javaHowdyTalky_ja.javaHowdyTalky_en.java
はじめに,ロードするコンポーネントを作成します。ありきたりですが,「Hello, World!」を戻すコンポーネントを作ってみることにしましょう。
まずはコンポーネントのファサードになるインタフェースです。インタフェース名はHowdyTalkyとしました。
      public interface HowdyTalky {
    public String sayHello();
}  
これを実装するクラスを二つ作ってみます。英語版と日本語版です。はじめに英語版。
      public class HowdyTalky_en implements HowdyTalky {
    public String sayHello() {
        return "Hello, World!";
    }
}  
次に日本語版です。
      public class HowdyTalky_ja implements HowdyTalky {
    public String sayHello() {
        return "こんにちは,世界";
    }
}  
他愛もないプログラムなので,解説するまでもないですね。
これらをJARファイルにまとめましょう。その前に,META-INF/servicesディレクトリに一つのファイルを作成する必要があります。インタフェースの名前と同一のファイル,つまりここではHowdyTalkyというファイルです。HowdyTalkyファイルの中には使用する実装クラスを記述します。
ここではHowdyTalky_enとHowdyTalky_jpのどちらかになりますが,はじめは英語にしておきましょう。
      HowdyTalky_en  
このファイルもふくめてJARファイルを作成します。
  [size=0.9em]    C:\temp>jar cvf talky.jar META-INF\services\HowdyTalky HowdyTalky*.class  
これでコンポーネントはできました。
続いて,このコンポーネントを使うクラスを作成します。ここではHowdyTalkyLoaderという名前を付けました。
  [size=0.9em]    public HowdyTalkyLoader() {
    ServiceLoader<HowdyTalky> loader = ServiceLoader.load(HowdyTalky.class);

    for (HowdyTalky talky: loader) {
        System.out.println(talky.getClass());
        System.out.println(talky.sayHello());
    }

}  
ServiceLoaderクラスは直接newで生成することはできません。生成するにはloadメソッド,もしくはloadInstalledメソッドを使用します。
loadメソッドは,loadメソッドの引数の型で修飾されたServiceLoaderオブジェクトです。ServiceLoaderクラスは,クラスパス上に存在するMETA-INF/servicesの中から,引数で指定された型と同じファイルを探し,インタフェースの実装クラスを見つけ出します。
あとは,SerciveLoaderクラスからiterationメソッドでイテレーションを取り出すか,拡張for文で実装クラスを取り出せます。
では実行してみましょう。
      C:\temp>java -cp talky.jar;. HowdyTalkyLoader
class HowdyTalky_en
Hello, World!

C:\temp>  
ちゃんとWEB-INF/services/HowdyTalkyファイルに書いたようにHowdyTalky_enクラスが実行されました。
それではHowdyTalkyファイルをHowdyTalky_jaに書き換えて実行してみましょう。
      C:\temp>java -cp talky.jar;. HowdyTalkyLoader
class HowdyTalky_ja
こんにちは,世界

C:\temp>  
こんどはHowdyTalky_jaが生成されました。
では,二つ並べてみたらどうでしょうか。
      HowdyTalky_en
HowdyTalky_ja  
二つ並べる場合には,1行に1クラスになるようにします。これで実行すると...
      C:\temp>java -cp talky.jar;. HowdyTalkyLoader
class HowdyTalky_en
Hello, World!
class HowdyTalky_ja
こんにちは,世界

C:\temp>  
ちゃんと二つのクラスが生成されました。同じように,HowdyTalky_enと書かれたHowdyTalkyファイルを使用したJARファイルと,HowdyTalky_jaと書かれたHowdyTalkyファイルを使用したJARファイルの二つをクラスパスに指定しても,二つのクラスのオブジェクトが正しく生成されます。
ちなみに,このサンプルでもわかるように,ServiceLoaderで生成できるのは,デフォルトコンストラクタ(引数のないコンストラクタ)を持つものだけです。
依存性を断ち切るところで,なぜ直接newでコンポーネントを生成してはいけないのでしょうか。例えば,HowdyTalky_enクラスを使うとわかっているのであれば,次のように直接記述してもいいのではないかということです。
      HowdyTalky talky = new HowdyTalky_en();  
しかし,この記述ではHowdyTalky_enクラスからHowdyTalky_jaクラスに変更する場合,ソースコードを変更しなくてはなりません。
そんなことでいちいちソースコードを変更したくはありません。ソースコードを変更したら,テストもしなおさなくてはなりませんし,変更の過程でバグが入ってしまうかもしれないからです。
できれば,実装クラスについて考えることなく,インタフェースだけで扱いたいですね。そのような場合に使われるのが,Factory Methodパターンというデザインパターンです。
ServiceLoaderクラスはファクトリ・メソッドを汎用に使えるようにしたものと考えることができます。DIコンテナほど汎用的ではありませんが,特定のコンポーネントの生成には威力を発揮します。
ServiceLoaderクラスは,Java SE 6でもいろいろなところで使われています。例を挙げてみましょう。
  
  • JDBC
  • Scriptエンジン
  • Annotationプロセサ
  • Attach API
  • Xalan
このようにコンポーネントを使った例はいろいろあります。DIを使うほどではないけれど,コンポーネントは可換にしたい場合にServiceLoaderクラスはピッタリです。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 15:10:58 | 显示全部楼层
Java SE 6におけるソフトウエア管理/監視のまとめ

3カ月にわたって,Java SE 6のソフトウエア管理に関する機能を紹介してきました。
Java EEではJ2EE 1.4から,Java SEではJ2SE 5.0からソフトウエア管理の機能が取り入れられてきました。バージョンを重ねるごとにその重要性は高まっています。
この背景には二つの要因があるのではないかと筆者は考えています。
  
  • コンピュータの性能が向上し,それほどパフォーマンスが問題にならなくなってきた
  • Javaを使用した大規模システムが一般化した
もちろん,個々のシステムではパフォーマンスの問題があることはわかっています。それでも,以前に比べると,パフォーマンスの問題がそれほど深刻ではなくなってきたのではないかと思うのです。ゴリゴリにチューニングするよりは,CPUをより高速なものに変えてしまうスケールアップなどで解決できてしまう場合も多くあります。
こうなると,処理とは関係のない余分な処理が少しぐらい走っていても,許されるような下地ができてきたのではないでしょうか。余ったCPUパワーを管理のための処理に割り当てられるようになったということです。
もちろん,プロファイラのように重い処理を常に走らせておくことはできません。しかし,JMXやMXBean程度であれば,それほど問題視されなくなってきていると思います。
もう一つのポイントが,大規模システムにJavaが使われているということです。
個人用の小さなプログラムでは,管理の必要はほとんどありません。しかし,重厚長大なシステム,24/7/365で動作し続けるシステムとなれば,話は別です。
正常に動作しているか,不穏な動きはないかなど,常に注意を払っていなければなりません。
また,コンポーネントの管理(デプロイやアンデプロイなど)もJMXを使って行われることが多くなりました。
Javaで作られたシステムは,幸か不幸かすべてJava VMの上で動作します。Java VMのレベルでJMXやMXBeanのような機能が組み込まれれば,その上で動作するシステムを全く変更しなくても,すぐに管理を行うことが可能です。
また,最近のアプリケーション・サーバーはJMXを重要なキーコンポーネントとして扱っています。例えば,GenonimoではMBeanを独自に拡張したGBeanを用いてコンポーネントの管理を行っています。
さらに,ソフトウエアの管理は,単なる管理・監視を超えて,今後は診断が一つのキーとなるのではないかと筆者は考えています。
取り上げなかった機能ところで,ソフトウエアの管理や保守に関係する新機能で取り上げなかったものがあります。
DTrace for Javaという機能です。
DTraceはSolaris 10で導入された機能で,カーネルやアプリケーションのトレースを可能にする機能です。DTraceにはプローブと呼ばれる,いわばデバッガのブレークポイントのようなものが設定されています。
例えば,システムコールやファイルの入出力などのプローブがあり,その数は3万以上になります。実際にシステムコールがコールされると,プローブから情報を取得でき,それをパフォーマンス・チューニングやデバッグに利用できます。
使用するプローブやどのような情報を取得するかなどは,C言語に似た「Dスクリプト」と呼ばれる言語で記述します。
Java SE 6では,DTraceでJavaもカーネルなどと一緒にトレースできるようになりました。
なぜこの連載でDTraceを取り上げなかったかというと,筆者がまだDTraceを使いこなしていないということに尽きます。すいません。
ただ残念なことに,DTraceはSolarisでしか使用できません。様々なプラットフォームで使えると筆者としてもうれしいのですが,それは難しいでしょうね。
さて,来月からは2006年12月11日にようやく正式リリースされたJava SE 6の新機能の中で,Ease of Development(EoD)に関するものを取り上げていきます。お楽しみに。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 15:12:46 | 显示全部楼层
GIFファイルの書き出し


あけましておめでとうございます。
昨年の2006年はJavaにとってエポックメーキングな年でした。5月にJava EE 5がリリースされ,12月にはJava SE 6がリリースされています。また,11月にはJavaがオープンソースになるというビッグニュースも飛び込んできました。
昨年に比べると,今年は端境期ということもあり,あまり大きな動きはないかもしれません。しかし,Java EE 6やJava SE7に対する準備が着々と進んでいるようです。例えば,Java SE7では,XMLリテラルやスーパーパッケージなど言語仕様の変更も予定されており,目が離せないところです。
さらに,最近ではJava EE/SEといったメインストリームではなく,オープンソースのプロジェクトから技術革新が起こることも多々あります。Apache Software FoundationThe Eclipse Foundationなどもしっかりとウオッチしていく必要がありそうです。
ということで,今年もいろいろなトピックを取り上げていこうと思います。本年もJava技術最前線をご贔屓のほどよろしくお願いします。
さて,年は変わりましたが,引き続きJava SE 6の新機能を取りあげていきましょう。
今回から,デスクトップやGUIに関する新機能を紹介していきます。今月はまずAWT関連の新機能です。
なお,今回からベータではなく,正式にリリースされたJava SE 6を使用していきます。
GIFファイルは読めるのだが...J2SE 1.4から導入されたImage I/Oを使えば,イメージ・ファイルの読み込み/書き込みを簡単に行うことができます。
簡単に行えるだけではなく,より詳細な制御も行うことも可能です。例えば,デジタルカメラで撮影したイメージ・ファイルに埋め込まれているExifメタデータを扱うことや,サムネイルやマルチイメージなども扱うことができます。
とても便利なAPIなのですが,残念ながらJ2SE 5.0までは提供されていなかった機能もあります。
その機能は,GIFファイルの書き込みです。さっそく,サンプルで確かめてみましょう。
サンプルはここからダウンロードできます:ImageCopySample.java
プログラムはとても簡単です。javax.imageio.ImageIOクラスを使用し,readメソッドで読み込み,writeメソッドで書き込みを行います。
writeメソッドの第2引数は出力形式になります。ここでは書き込むファイル名の拡張子をそのまま使用してしまいました。また,writeメソッドの戻り値はbooleanで,書き込みが失敗したときにfalseになります。
  public ImageCopySample(String src, String dest) {
    try {
        BufferedImage image = ImageIO.read(new File(src));

        int index = dest.lastIndexOf(".");
        String format = dest.substring(index+1);

        if (!ImageIO.write(image, format, new File(dest))) {
            System.out.println("書き込みに失敗しました");
        }
    } catch (IOException ex) {
        System.err.println("入出力に失敗しました");
    }
}
このサンプルを,まずJ2SE 5.0で実行してみます。
ImageCopySampleクラスは引数を二つ取ります。第1引数が読み込むイメージ・ファイル名,第2引数が書き込むイメージ・ファイル名になります。
      C:\temp>java ImageCopySample duke.jpg duke.gif
書き込みに失敗しました

C:\temp>  
書き込みに失敗してしまいました。duke.gifというファイルは作成されるのですが,サイズは0です。
次にJava SE 6で実行してみましょう。
      C:\temp>java ImageCopySample duke.jpg duke.gif

C:\temp>  
無事に書き込みができたようです。二つのファイルを比べてみると,GIFは色数が限られているので,Dukeの鼻が縞模様になってしまいました。
  
    [tr]        図1 変換前 duke.jpg        図2 変換後 duke.gif      [/tr]
  

なぜ,J2SE 5.0まではできなかったGIFファイルの書き込みがJava SE6ではできるようになったのでしょうか。これは技術的な問題ではなく,米Unisysが保持していたGIFの関連特許が2004年に失効したためです。新機能でも何でもありませんが,便利になることは確かです。
なお,java.netのjai-imageioプロジェクトでは,Java SE 6で扱えるJPEG,PNG,BMP,WBMP,GIF以外に,以下のフォーマットを扱うことができるライブラリを提供しています。
  
  • JPEG 200
  • PNM
  • TIFF
  • Raw
筆者にとってはTIFFが扱えるのがうれしいところです。ちなみにプロジェクト名についているjaiはJava Advanced Imagingというライブラリのことです。Image I/OはもともとJAIの一部だったのですが,切り離されてJava SEに組み込まれたのです。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-5-25 15:13:28 | 显示全部楼层
スプラッシュを表示させよう


アプリケーションを起動させたとき,なかなか表示が行われないとイライラしませんか。
起動にかかる時間は短ければ短いほどいいのですが,どうしても時間がかかってしまうことも多くあります。
そんなときに,なにかしら表示を行うことで,見せかけの起動時間を短くすることができます。そのために使用されるのがスプラッシュです。スプラッシュはアプリケーションの起動時に中央に表示されるイメージのことです。
スプラッシュを表示したとしても,実質的な起動時間に変化はありません。ところが,表示があるとないとでは,ユーザーのイライラ度がずいぶん変化するのです。
J2SE 5.0までは,スプラッシュを表示させる場合,デコレーションのないフレームを使用してきました。しかし,この方法ではスプラッシュが表示されるまでに時間がかかってしまい,本末転倒の結果になってしまいます。
Java SE 6は,やっと標準でスプラッシュの表示に対応しました。アプリケーション側の変更は何もいりません。アプリケーションの起動時にオプションを付けるか,もしくはマニフェスト・ファイルに記述するだけなのです。
スプラッシュの表示まずは起動オプションによるスプラッシュを表示する方法を試してみます。起動オプションでイメージ・ファイルを指定すると,それがスプラッシュとして使用されます。使用できるイメージ・ファイルはJPEG,PNG,GIFの3種類です。
スプラッシュを表示するためのオプションは-splashです。コロンの後にイメージ・ファイルを指定します。
      C:\temp>java -splash:duke.jpg Foo  
これだけで,スプラッシュを表示できます。
  
    [tr]        図1 スプラッシュの表示      [/tr]
  

アプリケーションのフレームが表示されるまで,スプラッシュは表示されます。
次にJARファイルの中にイメージ・ファイルを含めている場合です。この場合は,マニフェスト・ファイルで指定します。
      Manifest-Version: 1.0
Main-Class: Foo
SplashScreen-Image: duke.jpg  
ただし,マニフェスト・ファイルで指定する場合,Main-Classを記述したうえで,起動するときも以下のように-jarでJARファイルを指定する必要があります。
      C:\temp>java -jar foo.jar  
単にクラスパスにJARファイルを追加するだけではスプラッシュは表示されないので,ご注意ください。
スプラッシュを操作するスプラッシュを表示するだけなら,上記の方法で行うことができます。しかし,これだけじゃちょっと味気ないかもしれません。
よく,起動時にプログレスバーが表示されたり,ロードしているモジュールを表示しているアプリケーションがありますね。あれを実現してみましょう。
この場合は起動オプションだけというわけにはいきません。プログラムの変更が必要になってきます。
サンプルはこちらからダウンロードできます:SplashSample.java
スプラッシュを扱うためのクラスがjava.awt.SplashScreenクラスです。SplashScreenクラスの機能は多くないので,使うのは簡単です。
      public SplashSample() {
    try {
        Thread.sleep(5000L);
    } catch(InterruptedException ex) {}

    // スプラッシュの取得
    SplashScreen splash = SplashScreen.getSplashScreen();

    // スプラッシュに描画を行う
    Graphics2D g = splash.createGraphics();
    g.setFont(new Font("SansSerif", Font.BOLD, 24));
    g.setColor(Color.BLACK);
    g.drawString("もうちょい", 10, 230);

    // スプラッシュの更新
    splash.update();

    try {
        Thread.sleep(5000L);
    } catch(InterruptedException ex) {}

    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            JFrame frame = new JFrame("Test");
            frame.setSize(200, 200);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        }
    });
}
まず,SplashScreenオブジェクトを取得します。これにはstaticメソッドのgetSplashScreenメソッドを使用します。
SplashScreenオブジェクトが取得できたら,そこからGraphics2Dオブジェクトを生成します。後はこのGraphics2Dオブジェクトを使用して描画を行います。これは通常のグラフィック処理と同様です。
最後に変更したスプラッシュを更新します。更新にはupdateメソッドを使用します。
このサンプルを実行すると,5秒で描画が切り替わるはずです。前で述べたように,起動するときに-splashでイメージ・ファイルを指定するか,マニフェスト・ファイルで必ず指定してください。
スプラッシュが指定されていないと,getSplashScreenメソッドの戻り値がnullになってしまいます。
  
    [tr]        図2 スプラッシュの更新      [/tr]
  

このサンプルでは文字を描画しただけですが,プログレスバーなどを描画することもできます。もっとも,SwingのJProgressBarクラスを用いることはできないので,自分でプログレスバーを作らなくてはなりませんが。
また,SplashScreen#setImageURLメソッドを使用して,全く異なるイメージに置き換えることも可能です。
スプラッシュは簡単にできて,かつユーザビリティを向上させてくれるものなので,使わない手はないですね。ぜひご活用ください。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注~册

本版积分规则

小黑屋|手机版|咖啡日语

GMT+8, 2024-6-6 06:03

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表