今月は本題に入る前に、2012年4月26日にリリースされたJava SE 7u4について触れておきます。
Java SE 7u4では、Mac OS Xのサポートや、今まで実験的な機能とされてきたG1GCが正式にサポートされています。また、JRockitの機能のいくつかがHotSpotに移植されています。
もちろん、多くのバグフィックスやパフォーマンス向上も行われています。
これらのことから、Java SE 7が業務で使用するに十分な品質に達したと判断されたようです。今までjava.comではJava SE 7が配布されていなかったのですが、Java SE 7u4からjava.comで配布されるようになっています。
Java SE 6のEOL (End of Life)が今年(2012年)の11月に迫っているので、そろそろJava SE 7へ移行を考えていかなくてはいけないようです。
では、本題に入りましょう。今月紹介するのは、クラスローダです。
読者の皆さんは今さらクラスローダと思われるかもしれません。しかし、今までのクラスローダには問題があったのです。ここでは、その問題を説明する前に、まずクラスローダの動作について簡単に説明しましょう。
クラスローダの動作
クラスローダは、その名の通り、JARファイルなどからクラスをロードするために使用されるクラスです。
クラスロードの状況は、javaの起動オプションの-verbose:classで見ることができます。例えば、次のHello, World!を表示するプログラムを、hello.jarファイルにまとめて、実行してみます。
public class Hello {
public Hello() {
System.out.println("Hello, World!");
}
public static void main(String[] args) {
new Hello();
}
}
実行結果を次に示します。
C:\loader>java -cp hello.jar -verbose:class Test
[Opened C:\Program Files\Java\jre7\lib\rt.jar]
[Loaded java.lang.Object from C:\Program Files\Java\jre7\lib\rt.jar]
[Loaded java.io.Serializable from C:\Program Files\Java\jre7\lib\rt.jar]
[Loaded java.lang.Comparable from C:\Program Files\Java\jre7\lib\rt.jar]
[Loaded java.lang.CharSequence from C:\Program Files\Java\jre7\lib\rt.jar]
[Loaded java.lang.String from C:\Program Files\Java\jre7\lib\rt.jar]
<<省略>>
[Loaded java.security.BasicPermissionCollection from C:\Program Files\Java\jre7\
lib\rt.jar]
[Loaded Hello from file:/C:/hello/hello.jar]
[Loaded java.lang.Void from C:\Program Files\Java\jre7\lib\rt.jar]
Hello, World!
[Loaded java.lang.Shutdown from C:\Program Files\Java\jre7\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from C:\Program Files\Java\jre7\lib\rt.jar]
途中省略したのは、クラスロードが500行以上も続くからです。つまり、単純なHelloクラスでさえ、実行には500以上のクラスが必要になるわけです。
クラスをロードするには非明示的に行う方法と、明示的に行う方法があります。
// 非明示的なクラスロード
Class helloClass = Class.forName("Hello");
// 明示的なクラスロード
ClassLoader loader = ...;
Class helloClass2 = loader.loadClass("Hello");
java.lang.ClassLoaderクラスのloadClassメソッドは内部的にfindClassメソッドをコールします。findClassメソッドでは、JARファイルなどからバイナリデータを読み込み、defineClassメソッドをコールします。defineClassメソッドではバイナリデータからClassオブジェクトを構成します。
従って、クラスローダを自作する場合、ClassLoaderクラスのサブクラスを作成し、findClassメソッドをオーバライドします。findClassメソッドでバイナリデータを読み出したらdefineClassメソッドをコールするようにします。
しかし、クラスローダが自身が検索する範囲内に、ロードすべきクラスが見つからない場合はどうするのでしょうか。
その場合には、他のクラスローダに処理を委譲することができます。
また、ロードしたクラスはキャッシュをしておきます。loadClassメソッドでロード済みクラスを再びロードする場合は、このキャッシュから返します。
つまり、クラスローダは次の3種類の場所からクラスを検索します。
- キャッシュ(既にロードしたクラス)
- 親クラスローダ
- 自分自身
この3カ所の検索順序は仕様としては決まっていません。しかし、デフォルトではキャッシュ、親クラスローダ、自分自身の順に検索を行います。これをシーケンスで表したのが図1です。
ロードしているクラスのスーパークラスやフィールドなどにクラスなどがあれば、findClassメソッドの中から再びloadClassメソッドをコールし、クラスチェーンをすべてロードします。
