今月は本題に入る前に、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ファイルにまとめて、実行してみます。

リスト1●Hello.java
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以上のクラスが必要になるわけです。

 クラスをロードするには非明示的に行う方法と、明示的に行う方法があります。

リスト2●クラスのロード
        // 非明示的なクラスロード
        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メソッドをコールし、クラスチェーンをすべてロードします。

図1●クラスロードの検索シーケンス
図1●クラスロードの検索シーケンス