原文はこちら。
The original article was written by Mpirvuca Pirvu (Software Developer, IBM).
https://blog.openj9.org/2020/01/09/free-your-jvm-from-the-jit-with-jitserver-technology/
最先端のJava仮想マシン(JVM)は、Javaアプリケーションのスループットを向上させるために、ジャストインタイム(JIT)コンパイラを採用しています。しかし、JITコンパイラはコストがかかります。CPUとメモリの面でリソースを消費するため、Javaアプリケーションの円滑な実行を妨げる可能性があります。動的コンパイルの利点をすべて維持し、その欠点を取り除くことができればいいと思いませんか?これこそがまさにJITServerテクノロジーが提案していることです。Eclipse Openj9 JVMでは、JITコンパイラをJVMから切り離し、それをローカルかリモート・マシン上で独立したプロセスで実行するようにしました。JITServerと呼ばれるこのプロセスは、簡単にコンテナ化でき、サービスとしてクラウドで実行でき、KubernetesやDocker Swarmのような既製のオーケストレーターでインテリジェントに管理できます。
JITServer
https://www.eclipse.org/openj9/docs/jitserver/
Haven’t you just moved the JIT compilation overhead around?
見かけ上、JITServerはコンパイルのオーバーヘッドをある場所から別の場所に移動させますが、そのようなコンパイル統合戦略で以下のような利点を得ることができます。
- クライアントJVMでのコンパイルによるメモリ消費の急増が解消され、メモリ不足が発生する可能性が減ります。
- JITコンパイラがJavaアプリケーションからCPUサイクルを盗まないため、パフォーマンスの不調を解消し、サービス品質(QoS)を向上させ、より高速なスタートアップ/ランプアップ・エクスペリエンスにつながる可能性があります。
- ユーザーはJITコンパイルの予測不可能な影響を無視でき、JavaアプリケーションのCPU/メモリ・ニーズだけに集中できるため、アプリケーション・リソースのプロビジョニングが大幅に簡素化されます。
- コンテナのサイジングは、Javaアプリケーションのリソース使用量のみに基づいて行うことができ、結果として、より小さなコンテナ、増加したアプリケーション密度、および全体的なコストの削減につながります。
- JITのバグによるコンパイル時のクラッシュでJVMがダウンすることがなくなるため、アプリケーションの堅牢性が向上します。
- JITServerのコンテナインスタンスの数(およびそのサイズ)は、Javaアプリケーションとは独立してスケールアップ・ダウン(訳注:本来ならスケールアウト・インですが、原文に従って訳しています)増減できるため、コンパイルに費やされるリソースのコントロールの幅が広がります。1台のJITServerで数十台(数百台ではないにしても)のJVMのコンパイルニーズを満たすことができる点は言及すべきポイントです。さらに、N個のクライアントJVMからのリクエストが時間の経過とともにずれる典型的な使用パターンでは、サーバでのメモリ消費量はN倍に増加することはありません。
What’s the catch?
お察しの通り、全てがよい、というわけではありません。デメリットについては明言しておきたいと思います。このテクノロジーの最大の課題は、ネットワーク・レイテンシです。完璧な世界では、クライアントJVMがコンパイルリクエストを発行し、JITServerがコンパイルされたメソッド本体を返します。実際には、コンパイルプロセスの間、JITServerはクライアントに多くのクエリを送信し、実行時の環境(クラス、メソッド、フィールド、プロファイリング情報、クラス階層など)について問い合わせています。JITServerは、前後のネットワーク通信を減らすために積極的なキャッシングを採用していますが、要求された情報は実行時に変更される可能性があるため、キャッシングは部分的にしか有効ではありません。
そのため、一方ではネットワークのレイテンシによりコンパイルに時間がかかっていますが、他方ではJVMがJITのコンパイルにCPUサイクルやメモリを使わなくなっています。これらは拮抗する効果であり、最終的な結果は環境に依存します。アプリケーションがコンパイルの必要性と比較して、自由に使えるCPUとメモリのリソースが十分にある場合は、JITコンパイラを内蔵した従来のJVMの方がまだ良いでしょう。逆に、CPUとメモリに制約のある環境で多くのメソッドをコンパイルする必要がある場合は、JITServerテクノロジーをぜひ試してみてください。
I am sold! Tell me how to use it
この記事を書いている時点では、JITServerテクノロジーはプレビューとして提供されており、x86-64上のLinux上のJava8/11でサポートされています(ZとPowerシステム上のLinuxのサポートは近日中に予定されています!)。 我々は以下のLinuxディストリビューションで機能をテストしています。他の最新のLinuxディストリビューションでも同じように動作することを期待しています。
- Ubuntu 16.04
- Ubuntu 18.04
- RHEL 7.6
- CentOS 7.6
JITServerテクノロジーを使ってJDKを構築したい方のために、このブログの付録Aに詳細な手順を記載しました。リリース0.18.0から、AdoptOpenJDK (https://adoptopenjdk.net/)から入手可能なOpenJ9のビルドには、JITServerテクノロジーが組み込まれています。したがって、適切なコマンドラインオプションを使用するだけで、3つの異なるペルソナを切り替えることができます。
AdoptOpenJDK
https://adoptopenjdk.net/
- OpenJ9を通常のJVMとして起動するためのであれば、何も特別なことをする必要はありません。通常通りJavaコマンドを使用してください。
- OpenJ9をJVMクライアントモードで起動するためには、Javaコマンドラインで
-XX:+UseJITServer
を指定してください。サーバが利用できない場合、クライアントは素のJVMで動作することに注意してください。 - サーバモードでOpenJ9を起動するためには、
jitserver
コマンドを使用します。水面下では、着信するコンパイルリクエストをリッスンするJVMを起動します。
以下のコマンドラインオプションを使ってさらにJITServerやクライアントJVMプロセスを構成できます。
オプション | |
---|---|
-XX:JITServerPort=<Integer> | コンパイルリクエストの着信を受け付けるリスニングポートを指定 デフォルトは38400 |
-XX:JITServerAddress=<String> | サーバーの名前または IPアドレスを指定 既定値は localhost このオプションはクライアントJVMに対して有効 |
-XX:JITServerTimeout=<Integer> | ソケット操作のタイムアウト値(ミリ秒単位)を指定 デフォルト値は、JITServerで30000ms、クライアントJVMで2000ms ネットワークレイテンシが大きい場合、クライアントJVMの設定値を増やす必要がある。 |
I am concerned about security. Do you support traffic encryption?
その通りですね。必要に応じて、クライアントJVMとJITServer間のネットワーク通信をOpenSSL 1.0.xまたは1.1.xを使用して暗号化できます。暗号化を有効化するには、サーバーで秘密鍵と証明書を指定してください。
-XX:JITServerSSLKey=key.pem -XX:JITServerSSLCert=cert.pem
そして、クライアントで証明書を使います。
-XX:JITServerSSLRootCerts=cert.pem
キーと自己署名証明書を生成するには以下のようにしてください。
$ openssl genrsa -out key.pem 2048
$ openssl req -new -x509 -sha256 -key key.pem -out cert.pem -days 365
証明書、コモンネーム(Common Name, CN)は、証明書を使用するホストのFQDNである必要があります(クライアントと同じマシン上でサーバーを実行する予定であれば、CNはlocalhostでなければなりません)。
暗号化を行うと、かなりの量の CPU オーバーヘッドが発生し、コンパイルのレイテンシが増加することに注意してください。したがって、これを使用しないことができるかどうかを検討してください。クライアントとサーバーの間で交換される情報は、Javaクラス(バイトコード、クラス/メソッド/フィールド名、クラス階層)とプロファイリング情報に限定され、Javaヒープに保存されるユーザーデータは一切含まれないことを知っておくと便利です(静的なfinalフィールドは例外です)。サーバーで暗号化を有効にした場合、JITServerは暗号化されたリクエストのみを受け入れます。そのため、暗号化されていないトラフィックを扱うためには、JITServer の別のインスタンスを起動する必要があります。
Conclusion
OpenJ9 JVMに組み込まれたJITServer テクノロジープレビューは、JITコンパイルの負の影響からの救済をもたらします。ネットワークレイテンシが合理的である限り、このテクノロジーは、リソースに制約のある環境で実行されている多くのクラス/メソッドを持つJavaアプリケーションにとって非常に有用であることを証明するでしょう。したがって、これはコンテナサイズを縮小し、アプリケーションの密度を向上させ、全体的なコストを削減する効果的な方法になるでしょう。このテクノロジーの性能評価は、近日中に後続のブログで行う予定です。
A glimpse into performance of JITServer technology
https://blog.openj9.org/2020/02/11/a-glimpse-into-performance-of-jitserver-technology/
https://logico-jp.io/2020/12/18/a-glimpse-into-performance-of-jitserver-technology/
Appendix A: Building an SDK with JITServer technology.
ビルドの観点では、JITServerはOpenJ9 + protobuf (v3.7.1) と前提条件が同じです。後者は以下のコマンドで簡単にコンパイル、インストールできます(gcc 7.3以上が必要です)。
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/protobuf-cpp-3.7.1.tar.gz
$ tar -xvzf protobuf-cpp-3.7.1.tar.gz
$ cd protobuf-3.7.1
$ ./configure --disable-shared --with-pic
$ make
$ make install
$ ldconfig
configure
コマンドへ渡す --enable-jitserver
オプションを付けることを除き、ビルドプロセスは素のOpenJ9のビルドプロセスと同じです。
$ git clone https://github.com/ibmruntimes/openj9-openjdk-jdk8.git
$ cd openj9-openjdk-jdk8
$ bash get_source.sh
$ bash configure --with-freemarker-jar=/root/freemarker.jar --enable-jitserver
$ make clean
$ make all