JMX で Java Flight Recorder (JFR) を実行する

Java Flight Recorder (JFR) は Java Mission Control (jmc) や jcmd コマンドから実行できますが、今回は以下の MBean を使って JMX から実行してみます。

  • com.sun.management:type=DiagnosticCommand

この MBean は以下のような操作を備えており(戻り値は全て String)、jcmd コマンドと同じ事ができるようです。

  • jfrCheck
  • jfrDump
  • jfrStop
  • jfrStart
  • vmCheckCommercialFeatures
  • vmCommandLine
  • vmFlags
  • vmSystemProperties
  • vmUnlockCommercialFeatures
  • vmUptime
  • vmVersion
  • vmNativeMemory
  • gcRotateLog
  • gcRun
  • gcRunFinalization
  • gcClassHistogram
  • gcClassStats
  • threadPrint

(a) JFR の実行

JMX を使う方法はいくつかありますが、今回は Attach API でローカルの VM へアタッチし、startLocalManagementAgent メソッドJMX エージェントを適用する方法を用いました。

DiagnosticCommand には java.lang.management.ThreadMXBean のようなラッパーが用意されていないようなので GroovyMBean を使う事にします。

jfrStart の引数は jcmd コマンドと同じものを String 配列にして渡すだけのようです。(jfrStart 以外も基本的に同じ)

また、JFR の実行には Commercial Features のアンロックが必要です。

jfr_run.groovy
import com.sun.tools.attach.VirtualMachine

import javax.management.remote.JMXConnectorFactory
import javax.management.remote.JMXServiceURL

def pid = args[0]
def duration = args[1]
def fileName = args[2]

// 指定の JVM プロセスへアタッチ
def vm = VirtualMachine.attach(pid)

try {
    // JMX エージェントを適用
    def jmxuri = vm.startLocalManagementAgent()

    JMXConnectorFactory.connect(new JMXServiceURL(jmxuri)).withCloseable {
        def server = it.getMBeanServerConnection()

        // MBean の取得
        def bean = new GroovyMBean(server, 'com.sun.management:type=DiagnosticCommand')

        // Commercial Features のアンロック (JFR の実行に必要)
        println bean.vmUnlockCommercialFeatures()

        // JFR の開始
        println bean.jfrStart([
            "duration=${duration}",
            "filename=${fileName}",
            'delay=10s'
        ] as String[])
    }
} finally {
    vm.detach()
}

実行例

apache-tomcat-9.0.0.M4 へ適用してみます。

Tomcat 実行
> startup

以下の環境で実行しました。

  • Groovy 2.4.6
  • Java SE 8u92 64bit版
JFR 実行
> jps

4576 Jps
2924 Bootstrap

> groovy jfr_run.groovy 2924 1m sample1.jfr

Commercial Features now unlocked.

Recording 1 scheduled to start in 10 s. The result will be written to:

C:\・・・\apache-tomcat-9.0.0.M4\apache-tomcat-9.0.0.M4\bin\sample1.jfr

jfrStart は JFR の完了を待たずに戻り値を返すため、JFR の実行状況は別途確認する事になります。

出力結果 Recording 1 scheduled1 が recoding の番号で、この番号を使って JFR の状態を確認できます。

ファイル名を相対パスで指定すると対象プロセスのカレントディレクトリへ出力されるようです。 (今回は Tomcat の bin ディレクトリへ出力されました)

(b) JFR の状態確認

JFR の実行状況を確認するには jfrCheck を使います。

下記では recording の番号を指定し、該当する JFR の実行状況を出力しています。

jfrCheck の引数が null の場合は全ての JFR 実行状態を取得するようです。

jfr_check.groovy
import com.sun.tools.attach.VirtualMachine

import javax.management.remote.JMXConnectorFactory
import javax.management.remote.JMXServiceURL

def pid = args[0]
String[] params = (args.length > 1)? ["recording=${args[1]}"]: null

def vm = VirtualMachine.attach(pid)

try {
    def jmxuri = vm.startLocalManagementAgent()

    JMXConnectorFactory.connect(new JMXServiceURL(jmxuri)).withCloseable {
        def server = it.getMBeanServerConnection()

        def bean = new GroovyMBean(server, 'com.sun.management:type=DiagnosticCommand')

        println bean.jfrCheck(params)
    }

} finally {
    vm.detach()
}

実行例

recording 番号(下記では 1)を指定して実行します。

実行例1 (JFR 実行中)
> groovy jfr_check.groovy 2924 1

Recording: recording=1 name="sample1.jfr" duration=1m filename="sample1.jfr" compress=false (running)
実行例2 (JFR 完了後)
> groovy jfr_check.groovy 2924 1

Recording: recording=1 name="sample1.jfr" duration=1m filename="sample1.jfr" compress=false (stopped)

今回作成したサンプルのソースは http://github.com/fits/try_samples/tree/master/blog/20160519/