キーポイント
- Quarkus is an industry leader in startup time and memory utilization for native and JVM-based Java applications, leading to increased application density and reduced cloud costs.
- Building a native executable is as easy as adding a command-line flag to the build.
- We created Quarkus to run Java applications more efficiently instead of making a costly switch to Node.js or Golang.
- Developers can utilize their existing Java ecosystem expertise with build tools and APIs like Jakarta EE, MicroProfile, Spring, and more, with either imperative or reactive programming styles - or both!
- Kubernetes is a first-class deployment platform in Quarkus with support for its primitives and features.
- Quarkus unites the benefits of rapid development seen in scripting languages, the maturity of the Java ecosystem, and the efficiency of native compilation in a single runtime.
Javaはエンタープライズアプリケーションの多くを占めています。しかし、クラウドでは、Javaは一部の競合と比べてコストが割高です。ネイティブコンパイルにより、クラウドにおけるJavaのコストを下げることができます。起動がはるかに速く、使うメモリが少ないアプリケーションを作成できます。
そのため、ネイティブコンパイルでは、すべてのJavaユーザに向けた多くの疑問が挙げられます。ネイティブJavaによって、開発がどのように変わるのか。いつネイティブJavaに切り替えるべきか。いつはすべきではないか。そして、ネイティブJavaに対してどのフレームワークを使うべきか。このシリーズでは、これらの疑問に答えます。
このInfoQの記事は「Native Compilations Boosts Java」シリーズの一部です。サブスクライブするとRSSで通知を受けられます。
Kubernetes Native Javaとは何か
「サービスの可用性と応答時間を犠牲にせずに、3倍の密度でデプロイを実行できます。」 - Lufthansa Technik
組織はKubernetesを採用し、より迅速にコスト効率よくビジネス価値を提供しています。なぜでしょうか。Kubernetesを使うと難しいタスクが簡単になります。負荷が増えると、アプリケーションが自動的に水平方向にスケーリングされ、ローリングアップグレードが実行されます。
しかし、このアプローチはJavaの従来の3層アーキテクチャモデルと相対するものです。従来のJavaでは、ヒープサイズが大きくなり、垂直方向のスケーリングとなり、プロセスが長時間実行されます。
Javaはこれまで、最大のシステムリソースを割り当てて保持し、時間の経過とともにオンデマンドで負荷条件に適応することで、並外れたパフォーマンスを実現してきました。
開発者は、時間実行が長く、処理が重く、しかし、非常に動的なJVMというJavaのモデルを喜んで採用しました。複数の多様なアプリケーションを実行しながら、豊富な宣言型プログラミングモデルを実現するためです。レイジースキャンとランタイムclasspath分析により、制御の反転(Inversion of Control)とボイラープレートコードの削減を実現しました。しかし、起動時間とメモリ使用量が犠牲になりました。ヒープサイズが非常に大きい大規模なモノリシックアプリケーションでは、これらの欠点はそれほど大きな問題ではありませんでした。起動時にペナルティを1回払うだけだからです。
しかし、これらの従来のJavaアプリケーションをKubernetesで実行すると、肥大化したJVMが多く発生し、すべてがリソースを大量に消費する動的ランタイムをホストし、頻繁に再起動します。また、再起動時には、変わりのない同じイメージを何度も分析することでCPUサイクルを浪費し、毎回同じ結果に至ります。この分析では、JVMがその存続期間中保持する大量のメモリを消費します。しかし、Kubernetesでは密度が重要です。実行できるアプリケーションインスタンスが多いほど、Kubernetesからの投資収益率は高くなります。
Kubernetes Native Javaは、JavaアプリケーションがKubernetesの機能に抵抗せずに、それらの機能を活かすことがその意味するところです。つまり、起動時間が長く、メモリを大量に消費するJVMではなく、すばやく起動する小さなプロセスなのです。
Quarkusの背景にある動機
「Quarkusを導入する前は、パフォーマンスと効率を向上させるために、多くの顧客がGoやnode.jsなどの代替となるスタックを検討し始めていました。そういった顧客は、新しい言語を選択したり、新しい開発者を雇ったり、既存のJava開発者を再トレーニングしたりすることにうんざりしていました。」 - Arijit Mazumdar, WiPro
組織と開発者は同じようにJavaに巨額の投資をしています。これは、開発者に馴染みがあり、愛されていて、素晴らしいツールとフレームワークを備えた、信じられないほど生産的な言語です。しかし、Kubernetesは新しい方向性を求めています。Javaの多くのメリットをあきらめるのは残念でな一方で、Node.jsやGolangなど、よりメモリ効率が高く起動時間が早いランタイムがJavaの包囲網に挑戦しています。
Quarkusチームは、ケーキをとっておくことと食べることの両方を試みようとしました。機能が豊富で成熟したJavaエコシステムのメリットと、Kubernetesの運用上の利点を融合させるのです。これを実現するには、Javaアプリケーションを実行時に静的に定義しつつ、一方で開発者がより高速に反復できるようにJavaの動的機能が利用できなければなりません。
組織は、クラウドを使ってアプリケーションデプロイのペースを上げています。同様に、今日のビジネスでは、ビジネス価値をより早く提供するために、より短い開発サイクルが必要です。Quarkusのアプローチでは「Developer Joy(開発者の満足)」に重点を置いており、反復とデプロイのペースの向上をサポートしています。そして、Javaがスクリプト言語よりも生産性が高いことを証明しています。
もちろん、生産性を高めることは、コーディングの速さだけではありません。最新のクラウドアプリケーションを構築するには、他のサービスと連携する必要があり、そこで複雑なYAMLを伴う接続方式を使うことでスピードが低下します。Kubernetes Native Javaには、Dev ServicesやZero Config DevelopmentなどのDeveloper Joy機能が不可欠であると考えました。これについては後で詳しく説明します。たとえば、データベースにアクセスするためのコードを記述すると、Quarkusは魔法のように起動し、入力した通りにバックグラウンドですべてを接続してくれます。
同様に、Kubernetesへデプロイするために、Kubernetes管理ブックを読んだり、数百行のYAMLを作成したりする必要はありません。Quarkusでは、Javaを知っているだけで十分であることが重要だと考えました。ビルド時にフラグ「-Dquarkus.kubernetes.deploy
」を追加すると、QuarkusによってアプリケーションがKubernetesにデプロイされます(ログインすることを忘れないでください)。必要に応じて、統合されているQuarkus Dev UIを使って、ブラウザーから直接デプロイをトリガーできます。
つまり、Quarkusは、JavaをネイティブバイナリとKubernetesアプリケーションを構築するための理想的な言語に変換するためにゼロから構築されています。QuarkusはKubernetes Native Javaなのです!
ビルド時にネイティブコンパイルが培われる
「Quarkusでは、物事がカバーの下で実行される方法を逆さまにする新しいパラダイムを導入しました。ネイティブをサポートし、すべてのリフレクションが実行時でなくコンパイル時に行われることは素晴らしいです!」 - Roberto Cortez, Talkdesk(引用した時点では、Red Hatに採用されています)
アプリケーションのライフサイクルを2つの異なるフェーズに分割することは、瞬時に起動する小型で軽量のプロセスを実現するための鍵です。これはKubernetes Nativeアーキテクチャに対する要件となります。従来、Javaアプリケーションのランタイム起動では、動的なデプロイ環境の要求を満たすために、複雑で、実行時間が長く、動的で、内部でイントロスペクションを行う一連の手順を実行します。この手順は、アプリケーションが起動するたびに繰り返されます。
コンテナイメージは変わらないため、実行時にこれらの手順を実行する必要はありません。ほとんどの動的な起動手順は、ビルド時に移行できます。実際の開始時に実行される処理は、はるかに小さくなり、大幅に高速になります。さらに、アプリケーションが必要としないコードを破棄することも可能です。出力を調整すると、必要なコードのみを含むよりスリムな実行可能ファイルになります。
Quarkusによって、組織がJavaネイティブ実行可能ファイルを評価・準備する間に、JVMの効率性のメリットをすぐに享受できます。ビルド時のメリットが普遍的に適用されるためです。このアプローチから利益を得るのはネイティブコンパイルだけでなく、従来のJVM/ホットスポットも同じです。より効率的なコードが少なくても、その形式に関係なく、出力はよりスリムで高速になります。それとは別に、事前ネイティブコンパイルは、次のレベルのメリットをもたらします。つまり、すべてが事前にわかっている閉じた世界では、コンパイラは、フィールド、変数、命令セットのレベルに至るまで、非常にきめ細かい最適化を行うことができます。
ただし、落とし穴があります。事前(AOT)コンパイラがその仕事を効果的に行うには、Javaコードが最終的に何をするかを理解する必要があります。Javaの動的な側面により、生産性が非常に高くなり、アプリケーションの動作がコンパイラから不透明になり、最適化の程度が制限されます。たとえば、インジェクションポイントを使うと、コードをシンプルかつ簡単に進化させることができます。しかし、AOTコンパイラにとって、これは式における未定義で未解決の穴となります。
たとえば、リフレクションを介してすべてのクラスを使用可能としてマークすることにより、ネイティブ実行可能ファイルを簡単に取得できます。しかし、その結果は期待を裏切る可能性があります。メモリ使用量と起動時間は、JVMよりもわずかに改善される程度となります。つまり、ネイティブコンパイルのすべてのメリットを発揮するには、完全に包括的なビルド時モデルが必要となります。Quarkusが採用しているもののようにです!
現在、Quarkusはビルド時にすべての依存関係を解決し、完全なクローズドワールドアプリケーションを生成します。上記のインジェクションの例は、AOTコンパイラによって完全に解決されます。これで、使うクラスが認識され、残りのコードを削除できます。
要約すると、GraalVMの最適化機能とQuarkusのビルド時機能を組み合わせると、メモリフットプリントと起動時間が可能な限り最小になります。そのオーバーヘッドを制限することは、Kubernetes Native Javaのメリットを最大限に活かすために重要です。
第一級ののネイティブコンパイル
「コンテナではJVMモードでQuarkusを使っていますが、将来的には、Kubernetesとサーバーレス環境ではネイティブモードでQuarkusを使うことをすでに計画しています。そこでは、ネイティブモードが理想的なのです。 - Edouard Lamotte, Sedona
Quarkusとその拡張機能(Quarkus向けに最適化されたライブラリ)はビルド時の最適化を採用しているため、どのようなアプリケーションもフットプリントの小さいネイティブ実行可能ファイルとしてビルドできます。Quarkusがすべての作業を引き受けます!他のソリューションでは、多くの場合、開発者の関与がもっと必要になります。ネイティブにコンパイルできる場合がありますが、アプリケーションが明示的にサポートされているAPIサブセットを使っている場合、あるいはコードにGraalVMヒントでアノテーションを付けている場合に限ります。GraalVMのルールに従っている場合でも、多くの場合、別のJSON設定を最新の状態に保つ必要があります。さらに、Quarkusを使うと、開発者はネイティブ実行可能ファイルに対して実行するテストを簡単に作成できます。そして、作成したものがネイティブ実行可能ファイルで期待どおりに実行されることを確認できます。
この技術を効果的に使えるようにするには、プラットフォームによるネイティブコンパイルの幅広いサポートが不可欠であると考えています。このため、ネイティブコンパイルをできるだけ簡単に採用できるようにどんな苦労も惜しみません。さらに、Quarkusに同梱されているすべての拡張機能がネイティブコンパイルをサポートしていることを保証します。すべての「Quarkiverse」拡張機能(コミュニティが提供する拡張機能のリポジトリ)もネイティブコンパイルをサポートすることを強く推奨しています。すべてのJavaライブラリがGraalVMのネイティブコンパイルと互換性があるわけではありません。ただし、Quarkusには膨大な数の拡張機能(500近くと数えています!)があるため、考えられるすべてのユースケースをカバーしている可能性があります。そうでない場合でも、ユースケースに合わせてQuarkus拡張機能を作成することは難しくありません。
Developer Joy
「Quarkusは、開発者エクスペリエンスにかなり驚くべき革新をもたらしてきました。その開発モードコンソールとTestcontainerのサポートは次のレベルのものです。」 - James Ward, Javaチャンピオン
前述のように、Kubernetes Native Javaは、無駄のない実行だけではありません。Javaの生産性を強化・拡張し、Golangなどの他のネイティブコンパイル言語よりも生産性のアドバンテージを拡大することも不可欠です。すべてのツール、フレームワーク、ランタイムによって、開発者の生産性が向上すると主張しています。しかし、Quarkusでは、Developer Joyを提供することで、それを次のレベルに引き上げます。これはどういう意味でしょうか。
Quarkusは、Java開発者の関心を維持する、楽しい開発者エクスペリエンスの提供に努めています。Quarkusでは、すべての開発者がアクティブコーディングに集中できるようにすることを目標としています。これは、停止し、多数のツールを実行し、何かが終了するのを待つことがいかに非生産的であるかを知っているためです。Quarkusは非常に生産的であるため、開発者は「what if」シナリオを試すように奨励されていると感じるでしょう。これは、これまで期限があるがゆえに許されていなかったことです。次のDeveloper Joy機能を検討してください。
- ライブコーディング すべてのコード変更がリアルタイムに反映されます。依存関係や設定ファイルを変更する場合でもです。強い型付けをあきらめることはありません。アプリケーションにアクセスするたびに、Quarkusはコードの変更を評価し、必要なバイトコードを再生成し、アプリケーションをバックグラウンドでリロードして、通常0.5秒以内に更新結果を返します。ライブコーディングは事実上すべてのコード変更とリファクタリングで機能します。その複雑さは関係ありません。IDEプラグインや特別なツールも必要としません。
- Devサービス Quarkusでは、Testcontainersを使うことで、データベース、キャッシュ、Kafkaなどの開発・テスト中にサービスが自動的にインスタンス化され、構成されます。シンプルに拡張機能を追加してコーディングを開始するだけです。コンテナがバックグラウンドで自動的にダウンロードして起動するためです!
- ゼロ設定開発 「設定より規約」は、Devサービスに設定が不要であることを意味します。KubernetesデプロイメントYAMLは、Minishift、OpenShift、汎用Kubernetesに関係なく、すべてのターゲットディストリビューションに対して個別に自動生成されます。
- 継続的テスト ファイルが保存されるたびにテストを自動的に実行します。すべてのテスト、失敗したテスト、コード変更に関連するテスト(Quarkusが把握しています)のいずれかを実行します。次の画像は、ライブコーディングによる変更の結果とその継続的テストの出力を示しています。これによりJava開発がスクリプト言語と同じくらい生産的になります。
- Dev UI 開発中は、WebブラウザでQuarkus拡張機能を視覚化して利用します。次の画像は、Dev UIコンポーネントを備えたサンプルアプリケーションを示しています。これにより、開発者は設定をライブで更新したり、CDI Beanを表示したり、OpenShiftにデプロイしたり、Swagger UIを開いたりできます。
- コマンドモード Dev UIを補完するもので、Quarkus開発者モードで開始されたターミナルでキーボードから同様のDev UI機能を使うことができます。例えば、JVMを再起動せずに1回のキーストロークでログレベルを変更できます。
- Quarkus CLI Quarkusプロジェクトを管理するためのコマンドラインツールです。CLIで、プロジェクトの生成・構築、依存関係の管理、テストの実行などを行うことができます。
- 標準とベスト・オブ・ブリードフレームワークのサポート Quarkusでは、MicroProfileなどの標準や、JAX-RS、JPA、JTAなどの一部のJakarta EE仕様、およびApache Camel、Hibernate、Kafka、Spring Compatibility APIなどをサポートします。
- Kotlinサポート Javaに加えて、QuarkusではJVM向けの人気の代替言語であるKotlinをサポートします。
- IDE統合 Quarkusプラグインは、IntelliJ、Visual Studio Code、EclipseなどのIDEで利用できます。コード補完の強化などの機能を備えています。ただし、QuarkusにはIDEは必要ありません。Developer Joyは、viやNotepadさえも含む、あらゆるエディターで動作します。
これらの機能を組み合わせることで、開発チームの動きが加速し、自分たちが最も得意とすることに集中できるようになります。これらのメリットにより、プロジェクトの実施がスピードアップし、顧客の要件をより迅速に満たすことができるようになります。最終的には、開発環境整備よりもビジネスロジックに多くの時間を費やして、アプリケーションの品質を向上させることができるようになります。
命令型(Imperative)とリアクティブ(Reactive)
「[Quarkusは]リアクティブや命令型を当然処理できます」 - LogicDrop
ランタイムパフォーマンスは重要です。リアクティブプログラミングモデルでは、トータルとしてのリソース使用率が低いため、はるかに効率的にスケールでき、応答性が向上します。しかし、最大のランタイムパフォーマンスを達成するために、書き直しは必要ありませんし、使いやすさと開発者にとっての柔軟性が失われることもありません。そのため、Quarkusは、Eclipse Vert.xツールキットをベースとした、統合されたブロッキング/リアクティブI/Oスタック上に構築されています。
Quarkusではスマートルーティングが実装されています。これによって、命令型APIとリアクティブAPIの組み合わせを自然に開発できるようになります。
アプリケーションがQuarkusリアクティブAPIを使う場合、コードはI/Oスレッドで実行されます。これにより、スレッドコンテキストの切り替えが減り、リソース消費を最小限に抑えながらスループットを最大化できます。
命令型APIを使って開発する場合、Quarkusはワーカースレッドで作業をディスパッチし、完了したら作業をI/Oスレッドに戻します。
同じアプリケーション(または同じクラス)でも、ブロッキングの命令型API、あるいは非同期のリアクティブAPIを使って開発してください!Quarkusでは、Quarkusスマートルーティングを使って、シームレスで自然に両方のアプローチが共存できるようなります。開発者は、コード行を作成する前に事前に選択する必要はありません。
さようならボイラープレート!
「開発者の生産性が30~40%向上[するのを目の当たりにしました]...」 - Christos Sotiriou, Vodafoneギリシャ
Quarkusコミュニティは、Quarkusプロジェクトに入力するものはすべて、非常に簡潔で理解しやすくあるべきで、カーソルキーばかりを使うようになってはならないと強く信じています。Quarkusには一般的なAPIが多く含まれており、Quarkusのビルド時の知識を活用するようにそのAPIは拡張されています。
たとえば、QuarkusはHibernateを「Panache」で拡張しました。Panacheは、最新のJavaと同じように自然にデータアクセスできるようにする一連のAPI拡張です。このコードフラグメントは、JPAエンティティを表現するために必要なもののすべてです。getter、setter、ボイラープレートはありません(そういったものが好みでなければ、ぜひ、ない状態を維持してください)!
@Entity
public class Person extends PanacheEntity {
public String name;
public LocalDate birth;
public Status status;
}
データベースへのクエリも簡単になりました。
List<Person> allPersons = Person.listAll();
Quarkus拡張機能は、他の拡張機能の存在を検出し、統合できます。そのため、作成する必要のあるコードの量を減らし、ベストパターンとベストプラクティスであると私たちが考えているものを優先します。
たとえば、「Panache」コードスニペットが、データソースとヘルスチェック拡張機能を含むアプリケーションに含まれている場合を考えます。その場合、データソース拡張機能によって、そのアプリケーションとKubernetes Readiness Probeに対するデータベースヘルスチェックが自動的に定義されます。その結果、データベースが利用できない場合には、Kubernetesからアプリケーションコンテナにトラフィックは送信されません。
さらに、同じアプリケーションにメトリクス拡張機能が含まれている場合には、データソース拡張機能によってPrometheusなどの監視ツール向けにメトリクスが自動的に公開されます。
第一級プラットフォームとしてのKubernetes
「これは、私が[Kubernetesの]サービス設定をする必要がなくなり、代わってQuarkusがするため、私はコードの書くことに戻れるという意味ですか。」 -- DevOps Engineer, LogicDrop
QuarkusはもともとKubernetes Nativeであり、Javaバイトコードとネイティブ実行可能ファイルの両方からランタイム効率を実現します。さらに、Quarkusは第一級プラットフォームとしてKubernetesをサポートし、次の拡張機能と機能を提供します。
- 設定 Quarkusは、Kubernetes APIを利用して、ConfigMapあるいはSecretに保存されている設定にアクセスできます。その際、ポッドのファイルシステムにマウントする必要はありません。
- アプリケーションヘルス MicroProfile Healthを使って、アプリケーションヘルスをKubernetesヘルスプローブに公開します。トラフィックリダイレクトとポッドの再起動の可能性があるためです。
- Kubernetesクライアント KubernetesオブジェクトをJavaオブジェクトモデルでラップし、それらのオブジェクトを操作するためのJava APIを提供します。
- サービス検出と負荷分散 アプリケーションは、Kubernetesクラスター内のDNSを利用して、Kubernetesラウンドロビン負荷分散を使った検出サービスをします。あるいは、QuarkusのStorkクライアント側の負荷分散フレームワークを使って、より高度な、あるいはカスタマイズされた負荷分散アルゴリズムを使うこともできます。
- シンプルになったKubernetesとKnativeの設定とデプロイ Quarkusは、KubernetesあるいはKnativeへのデプロイに必要なYAMLを生成し、後者ではKubernetesにサーバーレス機能が追加されます。YAML生成は、Quarkusプロパティを使ってカスタマイズできます。
- 可観測性 MicrometerあるいはMicroProfile Metricsを使って実行中のアプリケーションに関する分析を行い、MicroProfile OpenTracingまたはOpenTelemetryを使って複数のサービスにわたるトレースリクエストを追跡します。
- Function-as-a-Service(FaaS) Quarkusは、ポータブル関数を開発するためのAPIとしてFunqyを提供します。この関数はKnativeイベントによりKnativeで実行され、AWS Lambda、Azure Functions、Google Cloud FunctionsなどのFaaS環境で実行されます。
- リモート開発 Quarkusはリモートライブコーディングをサポートします。他のツールは必要なく、すぐに使用できます。Kubernetesポッド(あるいは他のリモート環境)で実行されているアプリケーションをライブコーディングできます。特別なIDEツールは必要ありません!
- Quarkus Operator SDK Quarkusを使うことでKubernetes Operatorの記述がシンプルになります。
Quarkusを始めよう
「Quarkusをすぐに使い始められました。Quarkusガイドは、1つのトピックに焦点を当て、要点をストレートに理解するのに非常に役立ちました。」 - Victor Gallet - DECATHLONのシニアデベロッパー兼Kafka Expert
さらに学んでいくための最良の方法は、あなたにとって最初のQuarkusアプリケーションを作成することです。始めるのに数分しかかかりません。たった4つのステップです!各ステップを順を追って説明しているスタートガイドを手に取ってください。Spring開発者は、Spring開発者向けの無料のQuarkus for Spring Developers電子ブックを読むことで、Quarkusをすばやく理解できます。
その後、code.quarkus.ioサイトを利用することをお勧めします。必要な拡張機能を選択してプロジェクトを生成するだけです。
結論
Kubernetes Native Javaは、どのようにしてJavaを使ってKubernetesモデルを取り込んでいくかを再定義することを目的としています。起動時間の高速化とメモリ使用率の低減によって、共有環境でのリソース効率が向上し、コストが削減されます。Quarkusでは、起動時の処理の多くをビルド時間に移動することでこれを実現しています。その結果、効率的なランタイムが実現され、リソース効率の高いアプリケーションでクラウドコンピューティングのコストを大幅に削減できます。さらに、組織は、現在のクラウドコンピューティングの予算を超えることなく、ビジネスに直接影響を与える新機能を提供できます。
ネイティブコンパイルのメリットを最大化するために、QuarkusはGraalVMと密に統合されています。アプリケーションごとに、事前コンパイルの最適化を最大限に活用するように調整することができます。Quarkusは、このプロセスを強化するためにGraalVMの拡張モデルを使っており、追加の設定をせずに済みます。
Quarkusは、Developer Joyを提供します。これは、Live Coding、Dev Services、継続的テストなどの機能によって、開発者がビジネス上の問題に対する取り組みに積極的に関与してもらうようにするものである。Quarkusを使った開発は生産性が非常に高いため、Java開発者は複数の問題解決アプローチを試し、ビジネスに最も影響を与えるアプローチを選択できます。
Quarkusでは、Kubernetesを第一級のプラットフォームと考えています。Quarkusでは、アプリケーションがKubernetesが提供するものを最大限に活用できるようにするAPIフレームワークを提供します。これにより、Kubernetesへの投資が最大化され、プラットフォームの統合を通じた市場投入までの時間が短縮されます。
Javaはエンタープライズアプリケーションの多くを占めています。しかし、クラウドでは、Javaは他の競合よりもコストが割高です。ネイティブコンパイルにより、クラウドにおいてJavaが安価になります。起動がはるかに速く、使うメモリが少ないアプリケーションを作成できます。
そのため、ネイティブコンパイルでは、すべてのJavaユーザに向けた多くの疑問が挙げられます。ネイティブJavaによって、開発がどのように変わるか。いつネイティブJavaに切り替えるべきか。いつすべきではないか。そして、ネイティブJavaにはどのフレームワークを使うべきか。このシリーズでは、これらの質問に答えます。
このInfoQの記事は、「Native Compilations Boosts Java」シリーズの一部です。サブスクライブするとRSSで通知を受けられます。