JRebel 3.0がリリースされたことに伴い、Zeroturnaround社のCTOであるJevgeni Kabanov氏にインタビューを行い、JRebelの内部動作や、適用性およびインテグレーションの問題について詳細を聞き、このテクノロジーからJava開発者が得られる恩恵について理解を深めた。
最近、彼は、series detailing JVM classloading and runtime class replacement(JVMのクラスローディングと実行時のクラスの置き換えの詳細に関する連載)という記事を公開した。この記事の情報は連載として少しずつ提供されており、我々とのコミュニケーションからも一部内容がとりいれられる。
開発中にJavaアプリケーションの再デプロイやリスタートの必要があると、生産性を低下させる不必要な遅延が発生する。JRebelは開発時のターンアラウンドタイムを削減することを目的としており、 複雑な実行時のクラスの置き換えを処理する特別なjavaagentをJVMの起動パラメータに追加することで実現している。
ターンアラウンドと生産性
生産性に関する議論のために、Zeroturnaround社は、1100人以上のJava開発者から統計データを収集した。このデータを公開し、データを利用することで、一時間あたりの待ち時間として、平均してビルドに6分間、アプリケーションの再デプロイに約10.5分間かかっていることを計算した。
待ち時間に費やされる直接的なコストに加えて、その他の隠れたコストが存在する。ソフトウェア開発のような複雑なタスクをこなす環境においては、コンテクストの切り替えは、大きな問題となる。開発のフローにおいて、短期記憶に保持しておかなければならない情報量は莫大なものだが、焦点がそれるとすぐに記憶は減退してしまう。もとのコンテクストを回復させるにも相当な時間が必要となる。既存の文献では、コンテクストの切り替えは、電話やメール、個人への依頼などの職場で仕事を中断させるものを取り上げている。しかし、ここまで見てきたように同じことがインフラによる原因にもあてはまる。
生産性がおちることによって、一日あたりの成功や達成の数が減り、開発者のモチベーションが低下するということもありえる。
再起動することなく、アプリケーションの中のクラスの変更を反映させるための課題に対処する方法はいくつかある。しかし、すべて別の問題を引き起こすのだ。
JVMのホットスワップ
バイトコードを置き換えるJVMのホットスワッピングが、昔から(2002年のJDK 1.4から)あるじゃないか、という人もいるかもしれない。ホットスワップには、大きな制約がある。例えば、デバッグモードでしか動かなかったり、メソッドの中の変更しか扱えないといったものだ。クラスのメンバを追加したり削除したりといった構造上の変更を許さないのだ。ホットスワップはクラスローダも意識しない。クラスを名前のみで識別し、既存のクラスにのみ、うまく動作するのだ。
Java 5からは、ホットスワッピングは、Instrumentation APIを使って利用可能になった。このAPIはJRebelでも利用している。しかし、基盤となるクラスローダとクラスを編成するためのみに利用しており、実際の再リードのプロセスでは利用していない。
JVM、特にガベージ・コレクタやメモリレイアウト、ホットスポットの複雑さから、透過的にバイトコードを置き換える汎用的なソリューションを提供するのは難しい。
問題の一つは、オブジェクトのインスタンスの状態が影響をうけてはならないというものだ。そうでないと、そのインスタンスは完全に置き換えられなければならなくなり、そのインスタンスへの参照もすべて同様に更新されなければならない(連鎖的な変更も含めてだ)。
アプリケーションサーバでの再デプロイ/ホットデプロイ
大半のサーブレットコンテナやアプリケーションサーバ、OSGi実装での再デプロイ機能は同じように動作する。
アプリケーション(もしくはその一部)を専用のクラスローダでロードする。クラスローダとともにロードされたクラスへの参照すべてを削除すれば、アプリケーションをアンロードして、あたらしいクラスローダを使って完全にリロードすることができる。インスタンスやクラス、クラスローダに対するすべての参照を完全にJVMから削除するのは不可能であることが多い。
この場合、古いアプリケーションが残り、利用されないだけである。従って再デプロイを繰り返すと、クラスローダのリークが、メモリ使用量の増大につながる。
ホットデプロイは、効果的にアプリケーションを再スタートする。アプリケーションの状態を保持するために、状態は事前に保存(つまりシリアライズ)され、「新しい」アプリケーションやモジュールに再適用もしくは再ロードされる。
GrailsやRIFEなどの現在のコンポーネントベースのフレームワークはコンポーネントの状態を管理することに責任をもっている。このことによって、新しいクラスローダによって、再ロードされたコンポーネントは問題とならない。新しいコンポーネントの状態はフレームワークで既に利用可能な状態にある。コンポーネントの粒度によって、非常に小さなバンドルをほぼ瞬時にリロードすることが可能となる。
このようにクラスを再ロードする場合の一般的な問題は、クラスの新しいバージョンと古いバージョンが同時に利用可能であり、フレームワークによって慎重に扱う必要がある点だ。
JRebel
2007年から提供されており、以前はJavaRebelと呼ばれていたJRebelは異なるアプローチを採用している。編成されたクラスローダによってクラスはロードされ、動的に迂回路が生成される。このことによって、すべてのインスタンスの識別子と状態を保持しながら再ロードを行うことが可能となる。アドバンス・コンパイル技術(抽象バイトコードに似たもの)に基づいた迂回路によって、一つのマスタクラスと複数の無名のJITサポートが有効なクラスが生成される。JRebelはパフォーマンスになるべく影響を与えないように、メソッド呼び出しを可能な限りそのままにしておく。いくつかの理由により、JDKの編成を避ける。再ロードされたクラスに対するリフレクションAPIをサポートするために、API呼び出しの結果は修正される。
追加のクラスを生成することによって、permgen空間への割り当てが20から40%増える。Jevgeni氏によると、より少ないクラスと深い継承階層では、よりメモリを使うだろうとのことだ。しかしこれは内部の実装に依存してこうなっているにすぎないので、それにあてにするべきではない。
パッケージのなかのクラスを再デプロイする際に、JRebelでは開発者が(rebel.xmlの設定よって)パッケージのファイル構成を開発ワークプレイスに対応づけて、変更されたクラスやその他のリソースを再ロードすることを可能にしている。
リソースの再ロードやリフレッシュについても同様に考えると、さまざまなフレームワークやコンテナの設定やメタデータの情報を更新できるようになる。様々なフレームワークに対して、正しいリフレッシュの方式を実現するために、オープンソースAPIが提供されており、JRebelプラグインが開発できるようになっている。GuiceやSpring、Tapestry 4、Struts 2、Wicked、Stripes、WebObjects向けのオープンソースのプラグイン実装が利用可能だ。これらは部分的に、リリース版に含まれている。
これら3つの再ロードアプローチの違いは、この比較表にまとめられている。
JRebel 3.0での改善点
4月16日にリリースされたJRebelのリリース3.0には、多くの重要な改善が含まれている。
- パフォーマンスの改善。2倍の起動速度とメモリ使用量の25%-30%の削減
- スタティックフィールドの追加とenumに対する変更のサポート(自動的に初期化)
- EJBサポートの改善と特にIBM WebSphere、JBoss、Oracle Weblogic向けのプラグイン。EJBインターフェースの変更、依存性注入のサポートおよび次にあげるEJB 3に対するサポートの強化。
- JPA - エンティティおよびアノテーション、設定に対する変更の反映を行うOpenJPAとHibernateプラグイン(現時点でベータ版で、デフォルトでは無効になっている)
- JSPスクリプトレットの完全なサポート、外部のコードの変更がスクリプトレットで即座に利用可能となる。
- JSFの設定とアノテーションの変更のためのMojarraプラグインのサポート
- SpringやGuice、Seam、Weld Groovy、Wicket、Google App Engine、AspectJ、Struts 1 and 2などのプラグインのサポートの強化
- 他のツールで利用されているJavassistやcglibのようなバイトコードフレームワークの特別なサポート
Jevgeni氏によると最後の点はトリッキーなものだ。メソッドをクラスに追加する際、バイトコードフレームワークのインボケーション・ハンドラは予期しない合成されたメソッド呼び出しを受け取る。従って、JRebel APIはバイトコードプロキシの再定義を可能とするredefineClass()
メソッドによって拡張されている。このAPIを使うことでプロキシ実装の一部を書き換えて動的なクラスの変更を行うことが可能となる。現在のところ、これらのフレームワークを特別にサポートする2つのプラグインが存在する。
残った問題の一つは、プロキシ・インターフェースの制限により、はるかに少ない結合が求められるJDKの動的プロキシだ。
JRebelのゴールは、任意のJavaベースのインフラとシームレスに連携するために、さらに適合性をあげていくことだ。ひとつの重要なステップは、EJB連携を改善することだ。
企業向けのインテグレーションのために、Zeroturnaround社は古いテクノロジー(Java 1.4やアプリケーションサーバの古いバージョン、EJB 1.xや2.x)を対象とし、ライセンス管理を集中化したJRebel Enterprise Add-Onを提供している。これは既存のユーザには無償のアップデートとして提供される。
ライセンスとサポート
JRebelは個人向け(59ドル)と商用ライセンス(149ドル)で利用可能だ。30日間無償で試すことができ、さらに30日間の返金保証がついている。
Zeroturnaround社はオープンソース開発者およびScala開発者向けの無償ライセンスを提供することで、オープンソースコミュニティを支援している。