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
いますが、単純な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を利用して、使用量を確認することができました。