JavaSE 6によるJMXの管理と、Attach APIの使い方



JMXによる「Java VMの健康管理」と「Attach API」について調べてみたことを記していきたいと思います。
JMXに関しては、あまりインターネットのブログ等で資料をみつけることができなかったので、
JavaSE 6のJava SE 監視および管理ガイド「第 2 章 JMX テクノロジを使用する監視と管理」を参考にしました。
Attach APIに関しては、ITproさんの「第10回 オンデマンドアタッチを実現するAttach API」を参考にしました。*1

Attach APIは、なにかというと「Attach API」ガイドの内容を引用してみたいと思います。

Attach API は、Sun Microsystems拡張機能で、JavaTM 仮想マシンに添付メカニズムを提供します。Java 言語で書かれたツールは、この API を使用してターゲットの仮想マシンに添付し、そのツールエージェントを仮想マシンにロードします。たとえば、管理コンソールに、仮想マシンの計測機構付きオブジェクトから管理情報を取得する管理エージェントが存在すると仮定します。管理コンソールは、管理エージェントのない仮想マシンで実行しているアプリケーションを管理する必要がある場合、この API を使用してターゲットの仮想マシンに添付し、エージェントをロードできます。

 

前提

  • JavaSE 6を利用している

 

JMXにアクセスできるようにServerとClientを実装してみる

第 2 章 JMX テクノロジを使用する監視と管理」を参考に忠実に実装してみました。*2
 

JMXにアクセスできるようにServerを実装してみる

MyApp.java

 System.setProperty("java.rmi.server.randomIDs", "true");
 System.out.println("Create RMI registry on port 3000");
 LocateRegistry.createRegistry(3000);
 
 // Retrieve the PlatformMBeanServer.
 //
 System.out.println("Get the platform's MBean server");
 MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

 // Environment map.
 //
 System.out.println("Initialize the environment map");
 HashMap<String,Object> env = new HashMap<String,Object>();

 System.out.println("Create an RMI connector server");
 JMXServiceURL url =
     new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:3000/jmxrmi");
 JMXConnectorServer cs =
     JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);

 System.out.println("Start the RMI connector server");
 cs.start();

HashMap envを「JMXConnectorServerFactory.newJMXConnectorServer」の第二引数に渡して
いますが、単純なJMXConnectorServerを作成する場合は、nullで良いです。

実行してみると、下記のようにログを出力し、JMXクライアントからアクセスできる環境を構築することが
できました。

Create RMI registry on port 3000
Get the platform's MBean server
Initialize the environment map
Create an RMI connector server
Start the RMI connector server


jconsoleから試しに、アクセスしてみます。
リモートアクセスのサービス名に「service:jmx:rmi:///jndi/rmi://:3000/jmxrmi」を入力し、接続します。

 
うまく接続できると下記のような画面が表示されます。

 

JMXにアクセスできるようにClientを実装してみる

MyApp2.java

 // ロードしたエージェントがオープンした MBeanServer に接続
 
 JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:3000/jmxrmi");
 System.out.println("url="+url);
 
 JMXConnector connector = JMXConnectorFactory.connect(url, env);
 MBeanServerConnection connection = connector.getMBeanServerConnection();
 
 System.out.println("connector="+connector);
 System.out.println("connection="+connection);
 
 // MBeanServer から MXBean を取得
 MemoryMXBean memoryMXBean = ManagementFactory.newPlatformMXBeanProxy(
  connection,
  ManagementFactory.MEMORY_MXBEAN_NAME,
  MemoryMXBean.class);
  
 System.out.println("memoryMXBean="+memoryMXBean);
 
 // ヒープの使用量を出力
 MemoryUsage usage = memoryMXBean.getHeapMemoryUsage();
 System.out.println(usage);
 
 connector.close();

 
実行してみると、下記の様に表示されます。

 url=service:jmx:rmi:///jndi/rmi://:3000/jmxrmi
 connector=javax.management.remote.rmi.RMIConnector: jmxServiceURL=service:jmx:rmi:///jndi/rmi://:3000/jmxrmi
 connection=javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@743399
 memoryMXBean=MXBeanProxy(javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@743399[java.lang:type=Memory])
 init = 0(0K) used = 1504800(1469K) committed = 5177344(5056K) max = 66650112(65088K)

 

JMXにアクセスする際に、IDとパスワードを必須入力とする

MyApp側の実装

 HashMap<String,Object> env = new HashMap<String,Object>();
 env.put("jmx.remote.x.password.file", "PATH\\password.properties");
 env.put("jmx.remote.x.access.file", "PATH\\access.properties");
 JMXConnectorServer cs =
  JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);

 
MyApp2側の実装

 HashMap env = new HashMap();
 String[] credentials = new String[] { "admin" , "adminpasswd" }; 
 env.put("jmx.remote.credentials", credentials); 

 
「"admin" , "adminpasswd"」は、password.propertiesに記載されているアカウント情報になります。

 access.propertiesの内容
 guest   readonly
 admin   readwrite
 
 password.propertiesの内容
 guest  guestpasswd
 admin  adminpasswd

jconsoleで接続する際も、IDとパスワードが必要になります。
 

JSSE で使用するキーストアの作成

SSLを用いた通信に関しても試してみます。概念は、「3.2 SSL環境の構築手順」を参考に概念を理解してみました。
キーストア、トラストストアを利用したJMXアクセスを実現してみます。
 
キーストアを作成する為に、以下のコマンドを実行します。

 keytool -genkeypair -dname "cn=Mark Jones, ou=JavaSoft, o=Sun, c=US" -alias duke -keyalg RSA -validity 7 -keystore PATH\keystore

 
「keytool -list -v -keystore PATH\keystore」を実行して、生成したファイルの中身を確認します。

 キーストアのタイプ: JKS
 キーストアのプロバイダ: SUN
 
 キーストアには 1 エントリが含まれます。
 
 別名: duke
 作成日: 2011/03/22
 エントリタイプ: PrivateKeyEntry
 証明連鎖の長さ: 1
 証明書[1]:
 所有者: CN=Mark Jones, OU=JavaSoft, O=Sun, C=US
 発行者: CN=Mark Jones, OU=JavaSoft, O=Sun, C=US
 シリアル番号: 4d88319f
 有効期間の開始日: Tue Mar 22 14:20:31 JST 2011 終了日: Tue Mar 29 14:20:31 JST 2011
 証明書のフィンガープリント:
         MD5:  90:57:3E:53:55:DC:00:A1:47:********************************
         SHA1: 3A:CA:B0:BF:6C:C9:2D:44:FB:********************************
         署名アルゴリズム名: SHA1withRSA
         バージョン: 3

 
証明書を抜き出します。

keytool -export -alias duke -keystore PATH\keystore -rfc -file duke.cer

 

$cat duke.cer
 -----BEGIN CERTIFICATE-----
 MIIB/TCCAWagAwIBAgIETYgxnzANBgkqhkiG9w0BAQUFADBDMQswCQ**********************
 ChMDU3VuMREwDwYDVQQLEwhKYXZhU29mdDETMBEGA1UEAxMKTWFyay**********************
 NTIwMzFaFw0xMTAzMjkwNTIwMzFaMEMxCzAJBgNVBAYTAlVTMQwwCg**********************
 BAsTCEphdmFTb2Z0MRMwEQYDVQQDEwpNYXJrIEpvbmVzMIGfMA0GCS**********************
 iQKBgQCovlJ5+OvljiNvUuFo5v2CPxac0HP3gjUOv69h1SPxKrI0p3**********************
 Uj32NWr7GC60ZkUejduIDVoaShlr1ThhKsKJuNlHUjGcI2rTz59BKR**********************
 mwlVExvOjolt7g7JJabUmVGdxwIDAQABMA0GCSqGSIb3DQEBBQUAA4**********************
 absWJKvco1hUAJG0qlcvrih4MAMnhd6M4O8RXBINQtI7ER/6ilygIh**********************
 pALI4QESdL4fuwMpGvsviMtcbTYUabOrG+Tef0WCq1+aaAMGAuQvXP**********************
 -----END CERTIFICATE-----

 
証明書を新しいトラストストアにインポートします。

 keytool -import -alias dukecert -file duke.cer -keystore PATH\truststore

 
「keytool -list -v -keystore PATH\truststore」を実行して、トラストストアを確認します。

 キーストアのタイプ: JKS
 キーストアのプロバイダ: SUN
 
 キーストアには 1 エントリが含まれます。

 別名: dukecert
 作成日: 2011/03/22
 エントリのタイプ: trustedCertEntry
 
 所有者: CN=Mark Jones, OU=JavaSoft, O=Sun, C=US
 発行者: CN=Mark Jones, OU=JavaSoft, O=Sun, C=US
 シリアル番号: 4d88319f
 有効期間の開始日: Tue Mar 22 14:20:31 JST 2011 終了日: Tue Mar 29 14:20:31 JST 2011
 証明書のフィンガープリント:
         MD5:  90:57:3E:53:55:DC:00:A1:********************************
         SHA1: 3A:CA:B0:BF:6C:C9:2D:44:********************************
         署名アルゴリズム名: SHA1withRSA
         バージョン: 3

生成したキーストアとトラストストアを利用して、JMXにアクセスしてみます。
 
MyApp側の実装

 HashMap<String,Object> env = new HashMap<String,Object>();
 SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
 SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory();
 env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
 env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf);
 env.put("jmx.remote.x.password.file", "PATH\\password.properties");
 env.put("jmx.remote.x.access.file", "PATH\\access.properties");
 JMXConnectorServer cs =
  JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);

起動する際に、VM引数に、「-Djavax.net.ssl.keyStore=PATH\keystore -Djavax.net.ssl.keyStorePassword=password」を指定する。
 
jconsole でアクセスする場合は、下記の様にオプションを指定し、起動する。

 jconsole -J-Djavax.net.ssl.trustStore=D:\usr\home\dev1\al\build\truststore -J-Djavax.net.ssl.trustStorePassword=trustword

 
MyApp2を実行する際は、VM引数に、「-Djavax.net.ssl.trustStore=PATH\truststore -Djavax.net.ssl.trustStorePassword=trustword」を指定する。
実行すると下記のような感じで表示することができました。

 url=service:jmx:rmi:///jndi/rmi://:3000/jmxrmi
 connector=javax.management.remote.rmi.RMIConnector: jmxServiceURL=service:jmx:rmi:///jndi/rmi://:3000/jmxrmi
 connection=javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@77158a
 memoryMXBean=MXBeanProxy(javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@77158a[java.lang:type=Memory])
 init = 0(0K) used = 4219152(4120K) committed = 5177344(5056K) max = 66650112(65088K)

 

Attach API実行時に、ローカルで実行されているJavaアプリケーションをMBeanServerに登録する

Attach APIに関しては、ITproさんの「第10回 オンデマンドアタッチを実現するAttach API」を参考に
実行してみましたが、うまくいきませんでした。

その為、「JMXにアクセスできるようにServerとClientを実装してみる」を試してみて、どこか変更の余地があるか
調べてみました。
 
サンプルの元(AttachAgent.java

 public static final String SERVICE_URL
  = "service:jmx:rmi:///jndi/rmi://localhost/jmx";
 // MXBean を登録してある MBeanServer を取得
 MBeanServer server = ManagementFactory.getPlatformMBeanServer();
 
 // JMX Remote を使用したリモート接続の設定
 JMXServiceURL url = new JMXServiceURL(SERVICE_URL);
 JMXConnectorServer connector
  = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
 
  // コネクタの開始
  connector.start();

 
「connector.start()」処理でどうしてもExceptionが発生してしまうので、下記のとおり変更してみました。

サンプルの変更後(AttachAgent.java

 public static final String SERVICE_URL
  = "service:jmx:rmi:///jndi/rmi://:3000/jmxrmi";
 
 System.out.println("Create RMI registry on port 3000");
 LocateRegistry.createRegistry(3000);
 // MXBean を登録してある MBeanServer を取得
 MBeanServer server = ManagementFactory.getPlatformMBeanServer();
 
 // JMX Remote を使用したリモート接続の設定
 JMXServiceURL url = new JMXServiceURL(SERVICE_URL);
 JMXConnectorServer connector
  = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
 
  // コネクタの開始
  connector.start();

 
上記のようなソースにし、実行してみると下記のような感じでうまく登録できたようでした。

 java AttachSample
 5968: sun.tools.jconsole.JConsole
 
 java AttachSample 5968
 init = 0(0K) used = 7767776(7585K) committed = 8736768(8532K) max = 66650112(65088K)

java AttachSample」でローカルで実行しているJavaプロセスの一覧を表示します。
その後、「java AttachSample 5968」で、JConsoleにJMX経由でアクセスし、MemoryMXBeanの値を取得しました。*3
 

さくらVPSで実行しているCassandraで、Attach API経由で、MXBeanが確認できるか試してみる

 java AttachSample
 PID   VM
 766: org.apache.cassandra.thrift.CassandraDaemon
 
 java AttachSample 766
 init = 536870912(524288K) used = 21404216(20902K) committed = 510066688(498112K) max = 510066688(498112K)

上記のような感じで、Cassandra内のメモリ情報をMemoryMXBeanを利用して、使用量を確認することができました。


*1:記事のコードだと一部うまくいきませんでした。

*2:JMX リモート API を使用したアウトオブボックスの管理の模倣」を参考

*3:実行時の引数などは、省略してます