読者です 読者をやめる 読者になる 読者になる

かとじゅんの技術日誌

技術の話をするところ

クラスローダについて

Javaでは欠かせないクラスをロードするためのクラスローダ。普段意識していないかもしれませんが、しっかりと縁の下で支えているよね。(Java言語仕様を確認しながら書いてないので間違っていたらツッコミよろしくです)

クラスローダとは

Javaクラスローダー(英: Java Classloader)とは、Java仮想マシンの一部で、JavaクラスをJava仮想マシンに動的にロードする役割を持つ [1]。通常、クラスは必要になったとき初めてロードされる。Javaの実行系は、クラスローダーがあるおかげでファイルやファイルシステムについて知る必要がない。

大きく分けて以下の3つの種類があります。

  1. ブートストラップ クラスローダ
  2. エクステンション クラスローダ
  3. システム クラスローダ

ブートストラップは、Javaのコアライブラリ($JAVA_HOME/lib/rt.jar)のライブラリ、パッケージがjavaから始まるクラス群をロードするクラスローダ。通常、自作したクラスは自分でクラスパスを指定しないと実行させることができない。ブートストラップだけはクラスパスは指定しないで利用できる。エクステンションは、$JAVA_HOME/lib/extやjava.ext.dirsで指定されたパスにあるクラスをロードするためのクラスローダ。システムクラスローダは、クラスパス変数で指定したクラスをロードするクラスローダだ。

そして、これらには親子関係がある。ブートストラップ←エクステンション←システムという関係らしい。これはクラスのロードの順番に影響するということ。たとえば、子クラスローダでクラスをロードする場合は、まず親のクラスローダに処理を委譲する。そこで検索できなかった場合に、子のクラスローダで処理する。

アプリケーションサーバのクラスローダ

さて、アプリケーションサーバではどうなっているか。Tomcatでは以下のような感じ。

Apache Tomcat 6.0 - Class Loader HOW-TO
Apache Tomcat 7.0 - Class Loader HOW-TO

  1. Bootstrap
  2. System
  3. Common
  4. WebApp

の階層になっています。
Commonは$CATALINA_HOME/libのjarが読み込まれるクラスローダ。そして、WebAppはウェブアプリケーション単位で作られるクラスローダだ。

注意が必要なのは、以下のようにクラスの検索順を意識しておく必要があるってことだ。$CATALINA_HOME/libのクラスを自分のアプリケーション内のクラスを優先的に検索させることも可能。これを意識してないとドハマリするときあるので注意。

Therefore, from the perspective of a web application, class or resource
loading looks in the following repositories, in this order:

Bootstrap classes of your JVM
System class loader classes (described above)
/WEB-INF/classes of your web application
/WEB-INF/lib/*.jar of your web application
$CATALINA_HOME/lib
$CATALINA_HOME/lib/*.jar

ウェブアプリ(war)はTomcatのプラグイン的存在。アプリケーションサーバOSGi化は自然な流れ。

そもそもなんでこんな複雑なん?って話ですが、ヒントはここに。

Javaはクラスローダと呼ばれるそれほど賞賛されてはいない機能を持っていて、それによって、実行時のパスをさらに細分化することができる。一般には、全てのクラスはシステムクラスローダによってロードされる。しかしながら、実行時の空間を異なる複数のクラスローダに分けているシステムもある。良い例はTomcat(や他のサーブレットエンジン)であり、多くの場合一つのウェブアプリケーションに一つのクラスローダを持っている。これによって、ウェブアプリが普通に機能しながら、同じJVM助運の他のウェブアプリに寄って定義されたクラスを(偶然だろうとそれ以外の理由であろうと)見ることができないようになっている。
これが機能するための方法は各ウェブアプリが独自のクラスローダでクラスをロードすることであり、(局所的な)ウェブアプリの実装は他のウェブアプリの実装と衝突するようなクラスをロードしないことである。このことが要求することは、どんなクラスローダチェーンに対してもクラススペースが一貫しているということだ。これは、一度に2つのクラスローダからロードされた2つのUtil.classを保持することが、もしそのクラスローダが互いに不可視であるならば、可能であるという意味である。(それはまた、サーブレットエンジンが再起動することなく変更を際デプロイすることができるようにしているものでもある。クラスローダを捨てることで、そのクラスの参照も捨て去り、古いバージョンをガベージコレクションに食べさせることができる - これはサーブレットエンジンが新しいクラスローダを作成して、実行時に新しいバージョンのクラスを再ロードすることを可能にしている。)

ここでわかることは、WebApp1とWebApp2でUtil.classがそれぞれにロードされていても(WEB-INF/libに配置したjarにUtil.classが存在するような状況)、お互いにクラスローダが見えないなら、仮にバージョンが異なってもクラスローディングに競合が起きにくいようになっているということです。

ただ、昨今ではひとつのウェブアプリケーションでも複雑な要件を実現することも少なくないので、ウェブアプリケーション単位から、さらに役割ごとにクラスローディングの空間を分けて管理したほうがよいのではと思うことが多い。特にプラグイン指向なシステムでは上記のような問題がアプリケーション単体で起こり得る。つまり、同一アプリ上に同じLog4Jでもバージョンが異なるjarをローディングしたいという状況はよくある。こういう場合でも、OSGiフレームワークを使うとクラスロードの整合性を維持できるため簡単に実現できる。

Eclipse Virgo(旧Spring dm Server) http://www.eclipse.org/proposals/virgo/
TomcatをベースにOSGiに対応したアプリケーションサーバ。もともとSpringSourceが開発していたものをEclipseプロジェクトに寄贈したプロジェクト*1。まさに上述した考えをアプリケーションサーバに適用したモデルだ。JavaEEサーバではOSGi対応の動きがあるので、今後の動向を注目したい。

番外編

新規案件では、Virgoを使いたいですが、既存案件でTomcat上にOSGiランタイムを設定して起動をかけるのは非常に面倒です。なので、私のほうでは、OSGi FrameworkやBundleの集合を起動させたりマネージメントするためのランチャー用フレームワークApache Licenceで実装中です。*2 ほぼ完成していて、OSGiに対応していないサーブレットコンテナ上にOSGiアプリケーションを構築できるようになります。Servletインターフェイスを実装したサービスをOSGi Frameworkに登録すると動的にサーブレットの追加もできます。そして、WicketOSGiの助けを借りてバンドル(プラグイン)ベースのアプリケーションが実装できるようになります。バンドルに依存するDBスキーマについて、プラグイン単位でJiemamy APIで動的なプロビジョニングを可能にする予定。ちなみにプロジェクト名は獅子王(sisioh)プロジェクトです。(ウェブサイトがへぼいのでURL貼れないのですが、、、)
やることが多すぎなんで、気合入れていきたいと思います。

*1:といっても、コミッタはSpringSourceの方が多いです。SpringSourceとしてではなくEclipseとしての意思決定に基づき開発されていくのでしょう

*2:こっちは仕事の一環でやっています