こんにちは。寺岡です。
この記事は TECHSCORE Advent Calendar 2016 の 13 日目の記事です。
JConsole 使ってますか?
JConsoleを使うと、CPUやメモリのメトリクス、JMXのMBeanを使ったカスタムメトリクスの取得や管理系の操作を呼び出すことができます。
JMXはJSR3という歴史を感じさせる仕様だけあって、既存のフレームワークやライブラリにも多く採用されています。
GCを呼び出したり、Tomcatのセッション情報を覗いたり……わりとなんでもできてしまいます。
JConsoleの利用時は、対象のJavaプロセス内でJMX Serverが起動している必要があります。
ローカル環境やあらかじめ起動オプションでJMX Serverの設定をしている場合、JConsoleを立ち上げて接続先を選択するだけでOKです。
今回は、何の準備もしていないサーバに対してリモートからJConsoleで接続するまでの手順を紹介します。
その前に、ちょっとだけAttach APIの話
Javaには Attach API という機能があり、ローカルマシンで起動しているJVMの情報を取得したり、Instrumentation APIを使ったJavaエージェントをオンデマンドでロードすることができます。
あまり聞き慣れないAPIかもしれませんが、jstackのようなコマンドは内部でAttach APIを利用していたりします。
Attach APIのエージェント オンデマンドロードを使い、稼働中のJavaプロセス内にJMX Serverを起動させることで、JMXの設定をしていないJVMプロセスにもJConsoleで接続できるようになります。
具体的な方法
JConsoleで接続したいJVMプロセスが起動しているサーバで、おもむろにエディタを開いてこんなJavaファイルを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import java.io.File; import com.sun.tools.attach.VirtualMachine; public class JMXAgentLoader { public static void main(String [] pids) { for (String pid: pids){ try { String key = "com.sun.management.jmxremote.localConnectorAddress"; VirtualMachine vm = VirtualMachine.attach(pid); String connectorAddress = vm.getAgentProperties().getProperty(key); if (connectorAddress == null) { String agent = vm.getSystemProperties().getProperty("java.home") + File.separator + "lib" + File.separator + "management-agent.jar"; vm.loadAgent(agent); connectorAddress = vm.getAgentProperties().getProperty(key); } System.out.print("pid: " + pid); System.out.println(" connectorAddress: " + connectorAddress); vm.detach(); } catch (Exception e) { e.printStackTrace(); } } } } |
上記ファイルを作成したら、以下のようにコンパイルし実行します。
1 2 3 4 5 6 7 8 |
# JMXAgentLoaderをコンパイル javac -cp ${JAVA_HOME}/lib/tools.jar JMXAgentLoader.java # jpsなどで対象のJavaプロセスのPIDを確認 jps # コンパイルしたJavaファイルを実行してJMXServerのアドレスを取得 java -cp ".:${JAVA_HOME}/lib/tools.jar" JMXAgentLoader {JAVAプロセスのPID} |
実行すると、↓こんな感じのURLが表示されると思うので、クリップボードにコピーしておきます。
service:jmx:rmi://127.0.0.1/stub/ながーい文字列
これで対象サーバでJMXのリモート接続を受け付けるようになりました。
しかし、ループバックアドレスである127.0.0.1への接続しか受け付けていないため、外部から接続することはできません。
そこで、SSHクライアントのSocksプロキシを利用してリモートからの接続を行います。
以降はJConsoleを起動する環境で実行してください。(JConsoleはGUIが利用可能な環境で起動する必要があります)
1 2 3 4 5 |
# socksプロキシ(7777でリッスン)を有効にしてバックグラウンドでSSH接続を開く ssh -fN -D 7777 {対象サーバ} # socksプロキシを指定してjconsoleを起動 jconsole -J-DsocksProxyHost=localhost -J-DsocksProxyPort=7777 |
JConsoleの画面が開くので、「リモートプロセス」を選択して、先ほどコピーしたURLをペーストし、「接続」ボタンをクリックします。
メトリクスを見るなり、スレッドのスタックを覗くなり、GC実行するなり……色々できちゃいます!!
全部終わったらProxyに使っているバックグラウンドのsshプロセスをkillするのを忘れないようにしましょう。