Revised: May/05th/2008; Since: June/05th/2005
JVM はクラスをロードするのにクラスローダというものを使っています。クラスローダは一つではなく、JVM が使い分けている複数のクラスローダのほか、J2EE コンテナでは複数のクラスローダが階層的に追加定義されており、ユーザが独自に定義することも可能です。
セキュリティサンドボックス やクラスの発見、インスタンスと参照の管理、同一性/等価性の評価がクラスローダごとに行われています。クラスローダの親子関係を理解していないために起こる問題が少なくありません。特に、J2EE コンテナでは、コンテナ製品ごとにクラスローダの階層関係が異なっており、バージョンによっても変化があるため、ご利用の環境でどの順番でクラスがロードされるのか、当該クラスローダのスコープはどこかということを確認しておく必要があります。
J2SE で定義されているクラスローダが次の3つです。
クラスローダ間には親子関係があり、親クラスローダは子クラスローダを知りませんが、子クラスローダは親を知っています。子クラスローダに、クラスのロードの依頼があると、親に委譲し、最初に発見されたクラスがロードされます。もし、対象のクラスが、依頼を受けたクラスローダよりも子のクラスローダの責任範囲であると、当該クラスは発見されず、ロードが失敗します。
loadClass()findClass()ブートストラップクラスローダは、コアパッケージなどをロードするクラスローダで、それ自体としては親を持ちません。ユーザ定義クラスローダは、ブート時に起動するシステムクラスローダを親として派生します。
例えば、J2EEコンテナの場合、EARのユーティリティJAR をロードするクラスローダは、WARのウェブ・コンポーネントをロードするクラスローダとなっているため、ウェブ・コンポーネントはEARのユーティリティJARを呼ぶことができます。逆に、EARのユーティリティJARは、子であるWAR内のウェブコンポーネントを呼ぶことができません。
また、static 修飾されたフィールドは、クラスのロード時に一回実行されるので、クラスローダ内で一つとなります。クラスローダをまとめることで、ユーティリティクラスを共通クラスにすることができますが、思わぬ副作用が生じることもあるので、動作を理解してよく検討する必要があります。
クラスローダは、抽象クラス java.lang.ClassLoader の派生クラスです。例えば、コア・パッケージ内では、java.security.SecureClassLoader や java.net.URLClassLoader が、これのクラスの派生クラスとして定義されています。
次のコードは、クラスローダを取得する例です。test.ExtensionsClass は、拡張機能機能のオプションパッケージに追加するクラスです。
GetClassLoader.java:
import test.ExtensionsClass;
class GetClassLoader {
public static void main(String[] args) {
// Bootstrap class loader
System.out.println("●ブートストラップ・クラスローダ");
ClassLoader bootstrap = "".getClass().getClassLoader();
System.out.println(bootstrap);
if (bootstrap != null) {
ClassLoader parent = bootstrap.getParent();
System.out.println(parent);
} else {
System.out.println("親なし");
}
// 拡張クラスローダ
System.out.println("●拡張クラスローダ");
ExtensionsClass extObj = new ExtensionsClass();
ClassLoader extensions = extObj.getClass().getClassLoader();
System.out.println(extensions);
if (extensions != null) {
ClassLoader parent = extensions.getParent();
System.out.println(parent);
} else {
System.out.println("親なし");
}
// システム・クラスローダ
System.out.println("●システム・クラスローダ");
DemoClass myObj = new DemoClass();
ClassLoader systems = myObj.getClass().getClassLoader();
System.out.println(systems);
if (systems != null) {
ClassLoader parent = systems.getParent();
System.out.println(parent);
} else {
System.out.println("親なし");
}
// コンテキスト・クラスローダ
System.out.println("●コンテキスト・クラスローダ");
ClassLoader threads = Thread.currentThread().getContextClassLoader();
System.out.println(threads);
if (threads != null) {
ClassLoader parent = threads.getParent();
System.out.println(parent);
} else {
System.out.println("親なし");
}
}
}
// テスト用クラス
class DemoClass {
String getClassName() {
return "DemoClass";
}
}
拡張機能機構のインストール型オプションパッケージの作成:
C:\Program Files\Java\jre1.5.0_01\lib\ext>jar -cvf test.jar ./test マニフェストが追加されました。 test/ を追加中です。(入 = 0) (出 = 0)(0% 格納されました) test/ExtensionsClass.class を追加中です。(入 = 316) (出 = 225)(28% 収縮されまし た) test/ExtensionsClass.java を追加中です。(入 = 120) (出 = 93)(22% 収縮されました) C:\Program Files\Java\jre1.5.0_01\lib\ext>xcopy /vckyo test.jar C:\Progra~1\Java \jdk1.5.0_01\jre\lib\ext\test.jar C:test.jar 1 個のファイルをコピーしました
JRE と JDK では使用するクラスパスが異なるので、両方にコピーしています。本番環境とテスト環境、コンパイル環境でクラスパスが異なっているために発生する問題は少なくありません。
コンパイルと実行:
C:\java>javac GetClassLoader.java C:\java>java GetClassLoader ●ブートストラップ・クラスローダ null 親なし ●拡張クラスローダ sun.misc.Launcher$ExtClassLoader@35ce36 null ●システム・クラスローダ sun.misc.Launcher$AppClassLoader@11b86e7 sun.misc.Launcher$ExtClassLoader@35ce36 ●コンテキスト・クラスローダ sun.misc.Launcher$AppClassLoader@11b86e7 sun.misc.Launcher$ExtClassLoader@35ce36