JMC Agentで稼働時にJDK Flight Recorderイベントを収集

Red Hat で Solution Architect として OpenJDK を担当している伊藤ちひろ(@chiroito)です。

この記事は、Red Hat Developerのブログ記事、Collect JDK Flight Recorder events at runtime with JMC Agent | Red Hat Developer の翻訳記事です。


https://developers.redhat.com/sites/default/files/styles/article_feature/public/blog/2020/10/2020_JFR_Event-Instrumentation_Runtime_JMC_Agent-_Featured_Article.png?itok=J0Sjsgq4

JDK Flight Recorder、通称JFRは、OpenJDK 8u272から利用可能なイベントベースの本番の環境のプロファイラです。HotSpotネイティブの機能であるJDK Flight Recorderは、空間と時間の両方を使用する点で、極めて低いオーバーヘッドで動作します。

JDK Flight Recorderは、デフォルトで基本的なJavaランタイム情報を収集しますが、JFRのEvent APIを使用してカスタムイベントを収集できます。アプリケーションレベルのイベントを収集したい開発者は、アプリケーションのソースコードで積極的にイベントを定義し、インスタンス化します。

この記事では、JMC AgentとJMC Agent Pluginを使用して、イベントを発するコードでアプリケーションクラスを測定する方法を紹介します。JMC AgentをJDK Flight Recorder Event APIと共に使用する場合、JVMをシャットダウンしたりアプリケーションコードを再コンパイルする必要はありません。

:この記事では、JDK Flight Recorder ツール群の概要を含めていますが、JMC AgentとJMC Agent Pluginの紹介に重点を置いています。JDK Flight Recorderの詳細については、以前の記事であるGet started with JDK Flight Recorder in OpenJDK 8uをご参照ください。

JDK Flight Recorderツール群の概要

JMC Agentは、JDK Mission Control (JMC)プロジェクトのサブコンポーネントです。JDK Mission Controlは、JDK Flight Recorderとの相互作用と、JDK Flight Recorderがダンプした記録の分析に焦点を当てています。ここでは、主にJMC AgentとJMC Agent Pluginについて説明しますが、ツール群の概要を知っておくと便利です。

JDK Flight Recorder

JDK Flight Recorderは、もともとJRockitのJVM機能でした。JVMの主要な統計イベントをキャプチャし、フライトレコーディングファイルに記録してオフラインでの分析に利用します。2018年、Oracleはこの機能をJDK Flight Recorderという名前でオープンソース化し、OpenJDK 11に統合しました。JFRはOpenJDK 8にもバックポートされました。

開発者はJDK Flight Recorderを使用して、本番環境のJVMをプロファイリングします。JFRは、イベントをコンパクトなバイナリ形式で記録します。一目でわかるように、JFRはこれらのイベントをまずインメモリー・バッファーに保存します。定期的に、バッファをファイルシステムや他のストアにフラッシュします。JFRは「常にオン」の本番時のプロファイラーであることを意図しており、ほとんどのユースケースで2%未満のオーバーヘッドとなっています。

JDK Mission Control

フライトレコーディングをバイナリ形式で取得しても、それだけでは役に立ちません。OpenJDKはこれらの記録にアクセスし、その内容を複合するツールを提供します。しかし、JDK Mission Controlは、フライトレコーディングを分析するための最も強力で柔軟な方法です。

JDK Mission Controlは、JFRの記録を分析するために特別に設計されたGUIツールです。JDK Flight Recorderと共に、JMCは開発者にアプリケーションのランタイム特性の詳細な視点を提供します。この視点を使用して、パフォーマンスのボトルネックを特定し、JVMを細かく調整できます。また、JMCは、ローカルで動作しているJVMに接続し、新しい記録を取り、進行中の記録を制御する機能を備えています。

JMCは、Eclipse Rich Client Platformのアプリケーションです。様々なプラグインで拡張できます。また、JMCは、GUIアプリケーションとは別に、JFRの記録を読み込んで分析するための強力なスタンドアロンAPIを提供しています。

: JDK Mission Controlは、FedoraおよびRed Hat Enterprise Linux (RHEL) 7ではRed Hat Software Collections (RHSCL)経由で、RHEL 8 ではモジュール経由で、Windowsユーザー向けにOpenJDK developer portalから提供されています。JDK Mission Controlは、AdoptOpenJDKのようなダウンストリーム・ディストリビューションを通じて入手もできます。

JMC Agent

JMC Agentは、JMCのサブプロジェクトです。このバイトコード変成器を使用すると、実行中のアプリケーションにJFRの使用を宣言的に追加できます。JMC Agentを使用する場合、ソースコードにJFRの使用をプログラムする必要はありません。

エージェントは、JVMと同じアドレス空間で動作するため、非常に汎用性があります。アプリケーションの開始時にロードすることも、いつでも動的にロードすることも可能です。実行中の JMC Agentは、リモート Java Management Extensions (JMX) を通じて設定を行う MBean コントローラを公開します。

JMC Agent Plugin

JMC Agent Pluginは、JMC Agentの機能をJDK Mission Controlに統合するために開発されています。現在、外部プラグインとして別プロジェクトで開発されています。最終的には、JMCのサブモジュールとして再構築される予定です。開発者は「JMC Agent Plugin」を使用して、JMC Agentのライフサイクルを管理し、エージェントの動作を制御できます。

JMC Agent Pluginは初期の開発段階にありますが、重要な機能は既に使用可能です。以下のセクションでは、JMC AgentおよびJMC Agent Pluginについて、JDK Mission Controlで使用可能なエージェントプラグインの構築方法を含めて詳しく説明します。

JMC Agentの紹介

イベントを生成してJDK Flight Recorderに委ねる最も一般的な方法は、JDK 9で導入された標準のjdk.jfr.* APIを使用することです。jdk.jfr.* APIを使用してイベントを生成し、そのインスタンスを操作したい開発者は、jdk.jfr.Eventから手動で拡張する必要があります。JMC Agentから利用できる代替案は、宣言的でいつでも取り替え可能な構成を使用することです。JDK Flight Recorderは、JMC Agentや標準APIがイベントを生成するかどうかに関わらず、すべてのイベントを同じように扱います。

JMC Agentによるイベントインスタンスの作成と操作

JMC AgentはJava Instrumentation APIを使って、起動直後にClassFileTransformerを登録します。クラスローダがクラスのロードを開始すると、エージェントの ClassFileTransformer は、供給された設定に従って生成されたイベント委譲用のバイトコードを注入することによって、クラスのバイトコードを変換します。

また、エージェントがターゲットクラスへの参照を取得できる限り、クラスを再変換してリロードすることで、機能の使用を元に戻したり、更新したりできます。

JMC Agentの設計にあたっては、以下の要件を優先しました。

  • 宣言的であること:XML設定を使用して、注入するイベントを定義できます。
  • 目立ちすぎないこと: JMC Agentを実装するためにソースコードを変更する必要はありません。
  • 最小限のフットプリント:リフレクションを使用する必要がなく、イベント関連の関数呼び出しを行うだけです。
  • 痛みを伴わない:JVMをシャットダウンする必要がありません。

(下記参照)

これらの機能により、JMC Agentは安全に使用でき、副作用のない製品となっています。ソースが利用できない場合や、サービスやメンテナンスをすぐには停止できない場合の本番使用に最適です。JMC Agentは、アプリケーションのためにカスタム機能を作成する必要がある状況に特に適しています。

次に、JMC AgentとJMC Agent Pluginの使用方法について説明します。

イベントに記録する情報を設定する

JMC Agent の機能は、1 つまたは複数の機能に適用されます。XML 設定では、以下の情報を有効または無効にできます。

  • 機能の実行時の特性:開始時刻、終了時刻、継続時間。
  • 関数の入力と出力:パラメータ、戻り値、例外(ある場合)。
  • イベントのメタデータ:ラベル、説明、スレッドID、およびコールスタック。

式の評価については、JMC Agentを使用して、一次式(訳注:Javaの最も単純な式の集合のこと。参考Primary Expression)の限られたサブセットを有効または無効にできます。JMC Agentは、メソッドの呼び出し、配列へのアクセス、配列やインスタンスの生成を含むものを除き、すべてのJavaの一次式を受け入れます。この設計は、静的なチェックによって評価の安全性を確保し、一定時間のオーバーヘッドを保証することを目的としています。

エージェント設定ファイル

エージェント設定ファイルは、JMC Agentがアプリケーションのバイトコードをどのように計測するかを制御するXML表現です。

  • ドキュメントのルートは <jfragent> 要素です。現在の実装では、JMC Agentは名前空間を必要としません。<jfragent> 要素は内部のテキストを持たず、オプションで <config><events> 要素を囲みます。
  • <config> 要素には、すべてのイベントに適用されるグローバルオプションが含まれます。現在のところ、オプションは <classprefix>, <allowconverter>, <allowtostring> となっています。
  • <events> 要素は、任意の数の <event> 要素を囲んでおり、しばしばプローブと呼ばれます。各 <event> 要素は <class>, <method>, メソッド <descriptor> で識別される特定の機能ポイントに寄与します。
  • <parameter><field> 要素は、与えられたパラメータの値を記録し、それに応じた表現を行います。

: Java Fight Recorder Template(原文のまま)の一般的な使用方法を示す例については、JMC Agentのリポジトリを参照してください。

JMC Agentの制限事項

JMC Agentは、まだ開発の初期段階にあります。この記事を書いている時点では、次のような制約を解決しようとしています。

  • JMC Agentは、合成クラスを扱えません。
  • また、ネストクラスのプライベートフィールドにアクセスできません。
  • 新たにアップロードされた設定は、SystemClassLoaderで定義されたクラスでのみ動作します。
  • JMC Agentは現在、jdk.internal.misc.Unsafeを使用しているため、--add-opens(下記参照)でモジュールを開く必要があります。
  • JMC Agentは現在、JDK Mission Controlとは統合されていません。

:合成クラスの機能化は、間違いなくゴールではありません。 生成されたクラスの正確な名前を決定することはしばしば困難であり、それらを扱うことは困難です。ほとんどの場合、生成されたコードを機能化することは意味がありません。

JMC Agent Pluginの紹介

JMCチームは、ありがたいことに、拡張性とモジュール性に優れたEclipse Rich Client Platform上でJMCを開発しました。そのため、JMC Agent Pluginは、新しいクライアントソフトウェアを作成するのではなく、JDK Mission Controlにエージェント関連の機能を追加します。

一言で言えば、JMC Agent Pluginは、JDK Mission ControlがJDK Flight Recorderであるように、JMC Agentにとってのプラグインです。つまり、JMC Agentを制御するものです。JMC Agent Pluginは、現在も開発が進められていますが、すでにJMC Agentのライフサイクルを舵取りするのに役立っています。このプラグインを使用すると、ローカルのJVMでJMC Agentを起動し、JMX APIを介してJMC Agentに接続できます。接続すると、新規または変更された構成を適用できます。また、JMC Agent Pluginは、変換結果に関する生の情報を表示します。また、事前設定マネージャで定義済みの設定を管理したり、一連のウィザードで設定を作成・編集できます。

ライブ設定

ライブ設定ページは、JMC Agent Pluginに追加された最初の機能の一つです。これを使うと、JMC Agentに現在適用されている設定を表形式で確認できます。図1に示すように、Live Config画面の左側には、JMC Agentインスタンスで計測されているすべてのイベントがリストアップされています。右側には、各イベントの定義が表示されます。

https://developers.redhat.com/sites/default/files/blog/2020/08/live-config-page.png

図1:JMC Agent PluginのLive Configurationページ。

:将来的には、ライブ設定を事前設定マネージャに直接保存し、後で使用できるようにすることをサポートする予定です。

編集ウィザードと事前設定マネージャ

XML設定ファイルの編集は、初心者には難しく、経験豊富な開発者には退屈であり、また、エラーが発生しやすいものです。JMC Agent Pluginでは、そのような手間を省くために、編集ウィザードを導入しています。編集ウィザードは、事前編集されたマネージャと共に、事前設定として知られるローカル設定テンプレートの作成、変更、管理を効率化します。事前設定は、完全なXML構成を作成する必要のない構成やイベントオプションを必要としない場合に特に有効です。図2は、JMC Agent Pluginの事前設定編集ウィザードを事前設定マネージャ画面に表示したものです。

https://developers.redhat.com/sites/default/files/blog/2020/08/preset-and-event-wizards.png

図2:事前編集マネージャの編集ウィザードを使って、イベント構成の作成、変更、管理を行う。

ウィザードで事前設定を編集する代わりに、内蔵のXMLソースエディタを使って生の設定ファイルを手動で編集できます。今はまだありませんが、最終的にはXMLソースエディタにインラインでのエラーや警告表示を追加する予定です。

JMC AgentとJMC Agent Pluginについてご紹介しました。最後に、これらのツールを構築し、アプリケーションで実行するための簡単な手順をご紹介します。記事の最後には、デモ動画も掲載していますので、ぜひご覧ください。

JMC Agent のビルドと実行

JMC Agentをビルドして実行するには、JDK 7 以降のバージョンが必要になります。まず、JMCのソースツリーをGitHubリポジトリからクローンします。

$ git clone https://github.com/openjdk/jmc.git

そして、エージェントフォルダ内のMavenを使用します。

$ mvn clean package

ビルドが成功すると、targetディレクトリの下にエージェントのJARが表示されます。

$ ls target/org.openjdk.jmc.agent-*.jar

エージェントが静的に接続された状態でアプリケーションを実行するには、-javaagentオプションを使用して、エージェントのJARパスとオプションの設定パスを指定します。

$ java -XX:+FlightRecorde  -javaagent:<path-to-agent-jar>[=<path-to-agent-config>]  <rest-of-your-app-cmd>

JDK 9 以上での Unsafe クラスのエクスポート

JDK 9 以上の環境では、エージェントが使用する Unsafe クラスに --add-opens オプションを付けてエクスポートすることを忘れないでください。

--add-opens java.base/jdk.internal.misc=ALL-UNNAMED

例えば、JMC Agentに同梱されているサンプルプログラムを実行するには、次のように入力します。

$ java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED -XX:+FlightRecorder -javaagent:target/org.openjdk.jmc.agent-1.0.0-SNAPSHOT.jar=target/test-classes/org/openjdk/jmc/agent/test/jfrprobes_template.xml -cp target/org.openjdk.jmc.agent-1.0.0-SNAPSHOT.jar:target/test-classes/ org.openjdk.jmc.agent.test.InstrumentMe

JMC Agent Plugin を有効にして JMC をビルドする

JMC Agent PluginがJDK Mission Controlに同梱されるまでは、自分でビルドする必要があります。以下の手順を実行する前に、システムにJDK 8とMavenがインストールされていることを確認してください。

まず、JMCのソースツリーをクローンしてください。

$ git clone https://github.com/openjdk/jmc.git

そして、JMC Agent Pluginのソースツリーをクローンします。

$ git clone https://github.com/rh-jmc-team/jmc-agent-plugin.git

JMC Agent Pluginのソースツリーから、agent pluginとFeatureフォルダをJMCにコピーします。

$ cp -r jmc-agent-plugin/org.openjdk.jmc* jmc/application

JMCのルートディレクトリにパッチを適用する。

$ cd jmc
$ patch -p0 < ../jmc-agent-plugin/scripts/diff.patch

サードパーティの依存関係をローカルの p2 リポジトリに取り込み、localhost で利用できるようにします。

$ cd jmc/third-party && mvn p2:site && mvn jetty:run

最後に、別のターミナルで、JMCをコンパイルしてパッケージ化します。

$ cd jmc/core && mvn install && cd .. && mvn package

JMC Agentでもっとできること:ビデオプレゼンテーションとデモ

この記事は、Red Hat JDK Mission Control チームに向けて行った社内プレゼンテーションを基にしており、現在は一般公開されています。

youtu.be

デモは8分50秒からで、最近、最新の開発状況を反映して更新されました。このデモのコードは、GitHubリポジトリのDining philosophers demoにあります。

結論

JDK Flight Recorderは、本番時のプロファイラとしての役割を担っています。この文脈では、JMC AgentはJFRツール群の強力な追加要素となります。開発者としては、JMC Agentを使えば、JVMをシャットダウンすることなく、稼働しているアプリケーションを計測できます。アプリケーションを再デプロイするために、コードをリファクタリングして再コンパイルする必要もありません。

もちろん、JMC Agentを使用する代わりの方法もあります。例えば、Bytemanは、実行中のアプリケーションに同様の機能を注入するように設定できる強力なツールです。しかし、JMC Agentは、JFRイベントの計測に特化しているため、カスタムJFRイベントを必要とする問題に適しており、使いやすくなっています。まだまだ未完成ではありますが JDK Mission ControlにJMC Agent Pluginを追加することで、このツール群の使いやすさをさらに向上させることができます。

JMC AgentとJMC Agent Pluginは現在開発中で、新機能は常に計画・実装されています。このプロジェクトに貢献したい方、アイデアや提案、フィードバックがある方は、JMC-dev mailing listで議論に参加することができます。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。