NoClassDefFoundErrorの原因と対応(1)
前回の記事ではJavaクラスローダに関する現象を書いた。普段はあまり意識することの少ないクラスローダだけど、前回のような現象が現実に起きていることを考えると、クラスローダに対する多少の知識はあったほうが絶対に役立つ。
今回はクラスローダの様々な機能・特徴のうち、Webアプリケーションプログラマでも是非知っておきたい情報を2回に分けてまとめてみた。キチンと理解できればTomcatでNoClassDefFoundErrorが起きてもすぐに解決できるようになるはずだ。
Javaの関連記事:
- NoClassDefFoundErrorの原因と対応(2)
- OutOfMemoryError回避のためのJavaコーディング – 前編
- 本番リリース後にトラブル発生!魔のJavaマルチスレッド問題とは!?
- MissingResourceExceptionの解決法
- JavaMailの文字化け解決法
- プロプロセッサの使い方
- Eclipse スクラップブックの便利な使い方
クラスを読み込むのがクラスローダ
クラスローダは名前の通り、クラスファイル(*.class)やライブラリ(*.jar)からクラスを読み込むために使用される。と言っても、実際はクラスを表すファイルだけではなく、プロパティファイル(*.properties)やXMLファイルなど何でも読み込み可能だ。
クラスローダは1つだけではない
Java起動時には、役割に応じた3種類のクラスローダが存在する。
- ブートストラップ・クラスローダ:Javaのコアクラス(java.*パッケージ、javax.*パッケージ)をロードする
- 拡張クラス・クラスローダ:JREの拡張ディレクトリ(<JRE_HOME>/lib/ext)内のクラスをロードする
- システム・クラスローダ:CLASSPATH環境変数や-classpath引数で指定されたディレクトリやjarファイル内からクラスをロードする
Java実行時にはこれらのクラスローダを使ってクラスを読み込んでいる。で、どのクラスローダを使ってもクラスが見つからないときにNoClassDefErrorやClassNotFoundExceptionが発生する。
クラスローダはユーザーによる定義も可能
先ほど「Java起動時にクラスローダは3種類存在する」と言ったけど、クラスローダはアプリケーションごとに独自に定義することができる。
例えばTomcat 6の場合、Tomcat共通クラスローダとWebアプリケーションクラスローダの2種類のクラスローダがある。Tomcat共通クラスローダは<TOMCAT_HOME>/lib内のライブラリを読み込むためのもの。WebアプリケーションクラスローダはWEB-INF内のclassファイルやjarファイルを読み込むクラスローダで、Webアプリケーションごとに1つずつ作成される。
そして図のようにクラスローダはツリー構造をなしている。
「親のクラスローダ優先」が原則
クラスローダは委譲モデルを採用している。あるクラスローダでクラスが見つからなかったら、他のクラスローダがクラスの読み込みを行い、そこでも見つからなかったら次のクラスローダへ・・・という感じ。
そしてクラスローダの優先順は「子より親」。
子のクラスローダを優先させたほうが便利に思えるけど、セキュリティ上の理由で親クラスローダが優先されている。というのも、子のクラスローダを優先させた場合、java.*パッケージやjavax.*パッケージにある重要なクラスまで置き換えが可能となってしまい、Javaプログラムが正常に動作しなくなる可能性があるからだ。
Tomcatはちょっとだけ特殊
先ほど説明したルールによると、Tomcatでは最優先されるのがブートストラップクラスローダで、最後に回されるのがWebアプリケーションクラスローダになるように思える。
が、実際は次のように親の「Tomcat共通クラスローダ」よりも子の「Webアプリケーションクラスローダ」が優先される(赤丸が優先順位)。
例えば同名のクラスが <TOMCAT>/lib 内と <WEBAPP>/WEB-INF/lib 内の両方に存在するときは、Webアプリケーションクラスローダが優先されるから <WEBAPP>/WEB-INF/lib 内のクラスが使われることになる。
なぜこうなっているかと言うと、Servlet仕様書でWebアプリケーションサーバーの共通ライブラリよりもWebアプリケーション内のクラス・ライブラリを優先することが推奨されているから。そこでTomcatのクラスローダはServlet仕様に合致するようにクラスローダが実装されているのだ。
<WEBAPP>/WEB-INFが優先されるというルールはぜひ覚えておきたい。
実際のクラスローダを確認してみよう
ClassLoaderクラスを使えば、実際のクラスローダの階層を知ることができる。次のようなServletを書いてみた。
public class ClassLoaderDumpServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/plain"); PrintWriter writer = response.getWriter(); ClassLoader cl = this.getClass().getClassLoader(); while (cl != null) { writer.println(cl.getClass().getName()); cl = cl.getParent(); } } } }
Tomcat6での実行結果は次の通り(子クラスローダから親の順)。ブートストラップクラスローダはAPIから参照できないようだが、それ以外のクラスローダは表示されていた。
まとめ
今回はTomcatを使用した場合にどのようなクラスローダが存在しているのか、を中心に説明した。これでどのディレクトリにクラスパスが通っているのか、どういう順番に読み込まれるのか、全体像がつかめたのではないかと思う。
次回はクラスローダにさらに踏み込んでみます。
(参考)