クラスローダ

Since: June/05th/2005

JVM はクラスをロードするのにクラスローダというものを使っています。クラスローダは一つではなく、JVM が使い分けている複数のクラスローダのほか、J2EE コンテナでは複数のクラスローダが階層的に追加定義されており、ユーザが独自に定義することも可能です。

セキュリティサンドボックス やクラスの発見、インスタンスと参照の管理、同一性/等価性の評価がクラスローダごとに行われています。クラスローダの親子関係を理解していないために起こる問題が少なくありません。特に、J2EE コンテナでは、コンテナ製品ごとにクラスローダの階層関係が異なっており、バージョンによっても変化があるため、ご利用の環境でどの順番でクラスがロードされるのか、当該クラスローダのスコープはどこかということを確認しておく必要があります。

クラスローダの基本的な動き

J2SE で定義されているクラスローダが次の3つです。

クラスローダ間には親子関係があり、親クラスローダは子クラスローダを知りませんが、子クラスローダは親を知っています。子クラスローダに、クラスのロードの依頼があると、親に委譲し、最初に発見されたクラスがロードされます。もし、対象のクラスが、依頼を受けたクラスローダよりも子のクラスローだの責任範囲であると、当該クラスは発見されず、ロードが失敗します。

  1. クラスローダがクラスのロードを依頼される: loadClass()
  2. 既にロードされていないか調べる
  3. 無い場合は、親クラスローダ(無い場合はブートストラップクラスローダ)に処理を委譲(delegation)する
  4. ロードされていなければ探してロードしようとする: findClass()

ブートストラップクラスローダは、コアパッケージなどをロードするクラスローダで、それ自体としては親を持ちません。ユーザ定義クラスローダは、ブート時に起動するシステムクラスローダを親として派生します。

例えば、J2EEコンテナの場合、EARのユーティリティJAR をロードするクラスローダは、WARのウェブ・コンポーネントをロードするクラスローダとなっているため、ウェブ・コンポーネントはEARのユーティリティJARを呼ぶことができます。逆に、EARのユーティリティJARは、子であるWAR内のウェブコンポーネントを呼ぶことができません。

また、static 修飾されたフィールドは、クラスのロード時に一回実行されるので、クラスローダ内で一つとなります。クラスローダをまとめることで、ユーティリティクラスを共通クラスにすることができますが、思わぬ副作用が生じることもあるので、動作を理解してよく検討する必要があります。

クラスローダは、抽象クラス java.lang.ClassLoader の派生クラスです。例えば、コア・パッケージ内では、java.security.SecureClassLoaderjava.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


Copyright © 2005 SUGAI, Manabu. All Rights Reserved.
SEO [PR] !uO z[y[WJ Cu